diff --git a/.env.sample b/.env.sample index 99e6d1f..0dd4431 100644 --- a/.env.sample +++ b/.env.sample @@ -1,8 +1,5 @@ -STRIPE_WEBHOOK_SECRET= -STRIPE_SECRET_KEY= NODE_ENV= -DISABLE_LOGS= -DB_HOST= -DB_PORT= -DB_PASSWORD= -PORT= +STRIPE_SECRET_KEY= +STRIPE_WEBHOOK_SECRET= +PRIVATE_ROUTE_SECRET= +MIGRATE_ON_STARTUP= diff --git a/.eslintrc b/.eslintrc index 300c235..6523d9d 100644 --- a/.eslintrc +++ b/.eslintrc @@ -4,14 +4,23 @@ "parserOptions": { "project": "./tsconfig.json" }, - "plugins": ["@typescript-eslint"], + "plugins": ["@typescript-eslint", "header"], "extends": ["eslint:recommended", "plugin:@typescript-eslint/recommended"], "rules": { "no-console": 1, "no-unused-vars": "off", "@typescript-eslint/no-floating-promises": "error", "@typescript-eslint/no-misused-promises": "error", - "@typescript-eslint/no-unused-vars": ["error"] + "@typescript-eslint/no-unused-vars": [ + "error", + { + "vars": "all", + "args": "after-used", + "argsIgnorePattern": "^_", // Ignore variables starting with an underscore + "ignoreRestSiblings": true + } + ], + "header/header": [2, "./resources/license.header.js"] }, "ignorePatterns": ["lib/*", ".vscode/*", "bundle-jobs.cjs", "migrations/*"] } diff --git a/.husky/pre-commit b/.husky/pre-commit new file mode 100755 index 0000000..919789b --- /dev/null +++ b/.husky/pre-commit @@ -0,0 +1,4 @@ +#!/usr/bin/env sh +. "$(dirname "$0")/_/husky.sh" + +yarn lint-staged --verbose && yarn tsc --noEmit --pretty diff --git a/.mocharc.js b/.mocharc.js index 9537596..37ad21c 100644 --- a/.mocharc.js +++ b/.mocharc.js @@ -5,7 +5,7 @@ module.exports = { extension: ["ts"], require: ["ts-node/register/transpile-only", "tests/testSetup.ts"], - timeout: "3000", + timeout: "7000", parallel: true, recursive: true, }; diff --git a/.nvmrc b/.nvmrc index 8ddbc0c..7950a44 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -v18.16.0 +v18.17.0 diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000..6b450b0 --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,9 @@ +{ + "recommendations": [ + "arcanis.vscode-zipfs", + "dbaeumer.vscode-eslint", + "ms-azuretools.vscode-docker", + "esbenp.prettier-vscode", + "streetsidesoftware.code-spell-checker" + ] +} diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..2e71105 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,11 @@ +{ + "configurations": [ + { + "name": "Docker Node.js Launch", + "type": "docker", + "request": "launch", + "preLaunchTask": "docker-run: debug", + "platform": "node" + } + ] +} diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..88b9ad7 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,41 @@ +{ + "files.insertFinalNewline": true, + "editor.defaultFormatter": "esbenp.prettier-vscode", + "editor.formatOnSave": true, + "editor.formatOnSaveMode": "file", + "typescript.preferences.importModuleSpecifier": "relative", + "cSpell.words": [ + "abcdefghijklmnopqrxtuvwxyz", + "alexsasharegan", + "ario", + "bignumber", + "capturable", + "chargeback", + "Chargebacks", + "Clawback", + "Hodler", + "knexfile", + "livemode", + "nvmrc", + "openapi", + "Readables", + "sats", + "sslmode", + "trivago", + "typecheck", + "uncategorized", + "winc", + "winstons" + ], + "search.exclude": { + "**/.yarn": true, + "**/.pnp.*": true + }, + "eslint.nodePath": ".yarn/sdks", + "prettier.prettierPath": ".yarn/sdks/prettier/index.js", + "typescript.tsdk": ".yarn/sdks/typescript/lib", + "typescript.enablePromptUseWorkspaceTsdk": true, + "[dockerfile]": { + "editor.defaultFormatter": "ms-azuretools.vscode-docker" + } +} diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 0000000..257edfc --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,38 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "type": "docker-build", + "label": "ArDrive Upload Service Koa Server Container", + "platform": "node", + "dockerBuild": { + "dockerfile": "${workspaceFolder}/Dockerfile", + "context": "${workspaceFolder}", + "pull": true, + "buildArgs": { "NODE_VERSION": "$(cat .nvmrc | tr -cd [:digit:].)" }, + "tag": "upload-service-ecr", + "target": "dev" + } + }, + { + "type": "docker-run", + "label": "docker-run: release", + "dependsOn": ["docker-build"], + "platform": "node" + }, + { + "type": "docker-run", + "label": "docker-run: debug", + "dependsOn": ["docker-build"], + "dockerRun": { + "env": { + "DEBUG": "*", + "NODE_ENV": "development" + } + }, + "node": { + "enableDebugging": true + } + } + ] +} diff --git a/.yarn/plugins/@yarnpkg/plugin-workspace-tools.cjs b/.yarn/plugins/@yarnpkg/plugin-workspace-tools.cjs new file mode 100644 index 0000000..b9044a0 --- /dev/null +++ b/.yarn/plugins/@yarnpkg/plugin-workspace-tools.cjs @@ -0,0 +1,28 @@ +/* eslint-disable */ +//prettier-ignore +module.exports = { +name: "@yarnpkg/plugin-workspace-tools", +factory: function (require) { +var plugin=(()=>{var wr=Object.create,me=Object.defineProperty,Sr=Object.defineProperties,vr=Object.getOwnPropertyDescriptor,Hr=Object.getOwnPropertyDescriptors,$r=Object.getOwnPropertyNames,et=Object.getOwnPropertySymbols,kr=Object.getPrototypeOf,tt=Object.prototype.hasOwnProperty,Tr=Object.prototype.propertyIsEnumerable;var rt=(e,t,r)=>t in e?me(e,t,{enumerable:!0,configurable:!0,writable:!0,value:r}):e[t]=r,B=(e,t)=>{for(var r in t||(t={}))tt.call(t,r)&&rt(e,r,t[r]);if(et)for(var r of et(t))Tr.call(t,r)&&rt(e,r,t[r]);return e},Q=(e,t)=>Sr(e,Hr(t)),Lr=e=>me(e,"__esModule",{value:!0});var K=(e,t)=>()=>(t||e((t={exports:{}}).exports,t),t.exports),Or=(e,t)=>{for(var r in t)me(e,r,{get:t[r],enumerable:!0})},Nr=(e,t,r)=>{if(t&&typeof t=="object"||typeof t=="function")for(let n of $r(t))!tt.call(e,n)&&n!=="default"&&me(e,n,{get:()=>t[n],enumerable:!(r=vr(t,n))||r.enumerable});return e},X=e=>Nr(Lr(me(e!=null?wr(kr(e)):{},"default",e&&e.__esModule&&"default"in e?{get:()=>e.default,enumerable:!0}:{value:e,enumerable:!0})),e);var $e=K(te=>{"use strict";te.isInteger=e=>typeof e=="number"?Number.isInteger(e):typeof e=="string"&&e.trim()!==""?Number.isInteger(Number(e)):!1;te.find=(e,t)=>e.nodes.find(r=>r.type===t);te.exceedsLimit=(e,t,r=1,n)=>n===!1||!te.isInteger(e)||!te.isInteger(t)?!1:(Number(t)-Number(e))/Number(r)>=n;te.escapeNode=(e,t=0,r)=>{let n=e.nodes[t];!n||(r&&n.type===r||n.type==="open"||n.type==="close")&&n.escaped!==!0&&(n.value="\\"+n.value,n.escaped=!0)};te.encloseBrace=e=>e.type!=="brace"?!1:e.commas>>0+e.ranges>>0==0?(e.invalid=!0,!0):!1;te.isInvalidBrace=e=>e.type!=="brace"?!1:e.invalid===!0||e.dollar?!0:e.commas>>0+e.ranges>>0==0||e.open!==!0||e.close!==!0?(e.invalid=!0,!0):!1;te.isOpenOrClose=e=>e.type==="open"||e.type==="close"?!0:e.open===!0||e.close===!0;te.reduce=e=>e.reduce((t,r)=>(r.type==="text"&&t.push(r.value),r.type==="range"&&(r.type="text"),t),[]);te.flatten=(...e)=>{let t=[],r=n=>{for(let s=0;s{"use strict";var it=$e();at.exports=(e,t={})=>{let r=(n,s={})=>{let a=t.escapeInvalid&&it.isInvalidBrace(s),i=n.invalid===!0&&t.escapeInvalid===!0,o="";if(n.value)return(a||i)&&it.isOpenOrClose(n)?"\\"+n.value:n.value;if(n.value)return n.value;if(n.nodes)for(let h of n.nodes)o+=r(h);return o};return r(e)}});var ct=K((os,ot)=>{"use strict";ot.exports=function(e){return typeof e=="number"?e-e==0:typeof e=="string"&&e.trim()!==""?Number.isFinite?Number.isFinite(+e):isFinite(+e):!1}});var At=K((cs,ut)=>{"use strict";var lt=ct(),pe=(e,t,r)=>{if(lt(e)===!1)throw new TypeError("toRegexRange: expected the first argument to be a number");if(t===void 0||e===t)return String(e);if(lt(t)===!1)throw new TypeError("toRegexRange: expected the second argument to be a number.");let n=B({relaxZeros:!0},r);typeof n.strictZeros=="boolean"&&(n.relaxZeros=n.strictZeros===!1);let s=String(n.relaxZeros),a=String(n.shorthand),i=String(n.capture),o=String(n.wrap),h=e+":"+t+"="+s+a+i+o;if(pe.cache.hasOwnProperty(h))return pe.cache[h].result;let g=Math.min(e,t),f=Math.max(e,t);if(Math.abs(g-f)===1){let R=e+"|"+t;return n.capture?`(${R})`:n.wrap===!1?R:`(?:${R})`}let A=ft(e)||ft(t),p={min:e,max:t,a:g,b:f},k=[],y=[];if(A&&(p.isPadded=A,p.maxLen=String(p.max).length),g<0){let R=f<0?Math.abs(f):1;y=pt(R,Math.abs(g),p,n),g=p.a=0}return f>=0&&(k=pt(g,f,p,n)),p.negatives=y,p.positives=k,p.result=Ir(y,k,n),n.capture===!0?p.result=`(${p.result})`:n.wrap!==!1&&k.length+y.length>1&&(p.result=`(?:${p.result})`),pe.cache[h]=p,p.result};function Ir(e,t,r){let n=Pe(e,t,"-",!1,r)||[],s=Pe(t,e,"",!1,r)||[],a=Pe(e,t,"-?",!0,r)||[];return n.concat(a).concat(s).join("|")}function Mr(e,t){let r=1,n=1,s=ht(e,r),a=new Set([t]);for(;e<=s&&s<=t;)a.add(s),r+=1,s=ht(e,r);for(s=dt(t+1,n)-1;e1&&o.count.pop(),o.count.push(f.count[0]),o.string=o.pattern+gt(o.count),i=g+1;continue}r.isPadded&&(A=Gr(g,r,n)),f.string=A+f.pattern+gt(f.count),a.push(f),i=g+1,o=f}return a}function Pe(e,t,r,n,s){let a=[];for(let i of e){let{string:o}=i;!n&&!mt(t,"string",o)&&a.push(r+o),n&&mt(t,"string",o)&&a.push(r+o)}return a}function Pr(e,t){let r=[];for(let n=0;nt?1:t>e?-1:0}function mt(e,t,r){return e.some(n=>n[t]===r)}function ht(e,t){return Number(String(e).slice(0,-t)+"9".repeat(t))}function dt(e,t){return e-e%Math.pow(10,t)}function gt(e){let[t=0,r=""]=e;return r||t>1?`{${t+(r?","+r:"")}}`:""}function Dr(e,t,r){return`[${e}${t-e==1?"":"-"}${t}]`}function ft(e){return/^-?(0+)\d/.test(e)}function Gr(e,t,r){if(!t.isPadded)return e;let n=Math.abs(t.maxLen-String(e).length),s=r.relaxZeros!==!1;switch(n){case 0:return"";case 1:return s?"0?":"0";case 2:return s?"0{0,2}":"00";default:return s?`0{0,${n}}`:`0{${n}}`}}pe.cache={};pe.clearCache=()=>pe.cache={};ut.exports=pe});var Ge=K((us,Rt)=>{"use strict";var qr=require("util"),yt=At(),bt=e=>e!==null&&typeof e=="object"&&!Array.isArray(e),Kr=e=>t=>e===!0?Number(t):String(t),De=e=>typeof e=="number"||typeof e=="string"&&e!=="",Re=e=>Number.isInteger(+e),Ue=e=>{let t=`${e}`,r=-1;if(t[0]==="-"&&(t=t.slice(1)),t==="0")return!1;for(;t[++r]==="0";);return r>0},Wr=(e,t,r)=>typeof e=="string"||typeof t=="string"?!0:r.stringify===!0,jr=(e,t,r)=>{if(t>0){let n=e[0]==="-"?"-":"";n&&(e=e.slice(1)),e=n+e.padStart(n?t-1:t,"0")}return r===!1?String(e):e},_t=(e,t)=>{let r=e[0]==="-"?"-":"";for(r&&(e=e.slice(1),t--);e.length{e.negatives.sort((i,o)=>io?1:0),e.positives.sort((i,o)=>io?1:0);let r=t.capture?"":"?:",n="",s="",a;return e.positives.length&&(n=e.positives.join("|")),e.negatives.length&&(s=`-(${r}${e.negatives.join("|")})`),n&&s?a=`${n}|${s}`:a=n||s,t.wrap?`(${r}${a})`:a},Et=(e,t,r,n)=>{if(r)return yt(e,t,B({wrap:!1},n));let s=String.fromCharCode(e);if(e===t)return s;let a=String.fromCharCode(t);return`[${s}-${a}]`},xt=(e,t,r)=>{if(Array.isArray(e)){let n=r.wrap===!0,s=r.capture?"":"?:";return n?`(${s}${e.join("|")})`:e.join("|")}return yt(e,t,r)},Ct=(...e)=>new RangeError("Invalid range arguments: "+qr.inspect(...e)),wt=(e,t,r)=>{if(r.strictRanges===!0)throw Ct([e,t]);return[]},Qr=(e,t)=>{if(t.strictRanges===!0)throw new TypeError(`Expected step "${e}" to be a number`);return[]},Xr=(e,t,r=1,n={})=>{let s=Number(e),a=Number(t);if(!Number.isInteger(s)||!Number.isInteger(a)){if(n.strictRanges===!0)throw Ct([e,t]);return[]}s===0&&(s=0),a===0&&(a=0);let i=s>a,o=String(e),h=String(t),g=String(r);r=Math.max(Math.abs(r),1);let f=Ue(o)||Ue(h)||Ue(g),A=f?Math.max(o.length,h.length,g.length):0,p=f===!1&&Wr(e,t,n)===!1,k=n.transform||Kr(p);if(n.toRegex&&r===1)return Et(_t(e,A),_t(t,A),!0,n);let y={negatives:[],positives:[]},R=T=>y[T<0?"negatives":"positives"].push(Math.abs(T)),_=[],x=0;for(;i?s>=a:s<=a;)n.toRegex===!0&&r>1?R(s):_.push(jr(k(s,x),A,p)),s=i?s-r:s+r,x++;return n.toRegex===!0?r>1?Fr(y,n):xt(_,null,B({wrap:!1},n)):_},Zr=(e,t,r=1,n={})=>{if(!Re(e)&&e.length>1||!Re(t)&&t.length>1)return wt(e,t,n);let s=n.transform||(p=>String.fromCharCode(p)),a=`${e}`.charCodeAt(0),i=`${t}`.charCodeAt(0),o=a>i,h=Math.min(a,i),g=Math.max(a,i);if(n.toRegex&&r===1)return Et(h,g,!1,n);let f=[],A=0;for(;o?a>=i:a<=i;)f.push(s(a,A)),a=o?a-r:a+r,A++;return n.toRegex===!0?xt(f,null,{wrap:!1,options:n}):f},Te=(e,t,r,n={})=>{if(t==null&&De(e))return[e];if(!De(e)||!De(t))return wt(e,t,n);if(typeof r=="function")return Te(e,t,1,{transform:r});if(bt(r))return Te(e,t,0,r);let s=B({},n);return s.capture===!0&&(s.wrap=!0),r=r||s.step||1,Re(r)?Re(e)&&Re(t)?Xr(e,t,r,s):Zr(e,t,Math.max(Math.abs(r),1),s):r!=null&&!bt(r)?Qr(r,s):Te(e,t,1,r)};Rt.exports=Te});var Ht=K((ls,St)=>{"use strict";var Yr=Ge(),vt=$e(),zr=(e,t={})=>{let r=(n,s={})=>{let a=vt.isInvalidBrace(s),i=n.invalid===!0&&t.escapeInvalid===!0,o=a===!0||i===!0,h=t.escapeInvalid===!0?"\\":"",g="";if(n.isOpen===!0||n.isClose===!0)return h+n.value;if(n.type==="open")return o?h+n.value:"(";if(n.type==="close")return o?h+n.value:")";if(n.type==="comma")return n.prev.type==="comma"?"":o?n.value:"|";if(n.value)return n.value;if(n.nodes&&n.ranges>0){let f=vt.reduce(n.nodes),A=Yr(...f,Q(B({},t),{wrap:!1,toRegex:!0}));if(A.length!==0)return f.length>1&&A.length>1?`(${A})`:A}if(n.nodes)for(let f of n.nodes)g+=r(f,n);return g};return r(e)};St.exports=zr});var Tt=K((ps,$t)=>{"use strict";var Vr=Ge(),kt=ke(),he=$e(),fe=(e="",t="",r=!1)=>{let n=[];if(e=[].concat(e),t=[].concat(t),!t.length)return e;if(!e.length)return r?he.flatten(t).map(s=>`{${s}}`):t;for(let s of e)if(Array.isArray(s))for(let a of s)n.push(fe(a,t,r));else for(let a of t)r===!0&&typeof a=="string"&&(a=`{${a}}`),n.push(Array.isArray(a)?fe(s,a,r):s+a);return he.flatten(n)},Jr=(e,t={})=>{let r=t.rangeLimit===void 0?1e3:t.rangeLimit,n=(s,a={})=>{s.queue=[];let i=a,o=a.queue;for(;i.type!=="brace"&&i.type!=="root"&&i.parent;)i=i.parent,o=i.queue;if(s.invalid||s.dollar){o.push(fe(o.pop(),kt(s,t)));return}if(s.type==="brace"&&s.invalid!==!0&&s.nodes.length===2){o.push(fe(o.pop(),["{}"]));return}if(s.nodes&&s.ranges>0){let A=he.reduce(s.nodes);if(he.exceedsLimit(...A,t.step,r))throw new RangeError("expanded array length exceeds range limit. Use options.rangeLimit to increase or disable the limit.");let p=Vr(...A,t);p.length===0&&(p=kt(s,t)),o.push(fe(o.pop(),p)),s.nodes=[];return}let h=he.encloseBrace(s),g=s.queue,f=s;for(;f.type!=="brace"&&f.type!=="root"&&f.parent;)f=f.parent,g=f.queue;for(let A=0;A{"use strict";Lt.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 Pt=K((hs,Nt)=>{"use strict";var en=ke(),{MAX_LENGTH:It,CHAR_BACKSLASH:qe,CHAR_BACKTICK:tn,CHAR_COMMA:rn,CHAR_DOT:nn,CHAR_LEFT_PARENTHESES:sn,CHAR_RIGHT_PARENTHESES:an,CHAR_LEFT_CURLY_BRACE:on,CHAR_RIGHT_CURLY_BRACE:cn,CHAR_LEFT_SQUARE_BRACKET:Bt,CHAR_RIGHT_SQUARE_BRACKET:Mt,CHAR_DOUBLE_QUOTE:un,CHAR_SINGLE_QUOTE:ln,CHAR_NO_BREAK_SPACE:pn,CHAR_ZERO_WIDTH_NOBREAK_SPACE:fn}=Ot(),hn=(e,t={})=>{if(typeof e!="string")throw new TypeError("Expected a string");let r=t||{},n=typeof r.maxLength=="number"?Math.min(It,r.maxLength):It;if(e.length>n)throw new SyntaxError(`Input length (${e.length}), exceeds max characters (${n})`);let s={type:"root",input:e,nodes:[]},a=[s],i=s,o=s,h=0,g=e.length,f=0,A=0,p,k={},y=()=>e[f++],R=_=>{if(_.type==="text"&&o.type==="dot"&&(o.type="text"),o&&o.type==="text"&&_.type==="text"){o.value+=_.value;return}return i.nodes.push(_),_.parent=i,_.prev=o,o=_,_};for(R({type:"bos"});f0){if(i.ranges>0){i.ranges=0;let _=i.nodes.shift();i.nodes=[_,{type:"text",value:en(i)}]}R({type:"comma",value:p}),i.commas++;continue}if(p===nn&&A>0&&i.commas===0){let _=i.nodes;if(A===0||_.length===0){R({type:"text",value:p});continue}if(o.type==="dot"){if(i.range=[],o.value+=p,o.type="range",i.nodes.length!==3&&i.nodes.length!==5){i.invalid=!0,i.ranges=0,o.type="text";continue}i.ranges++,i.args=[];continue}if(o.type==="range"){_.pop();let x=_[_.length-1];x.value+=o.value+p,o=x,i.ranges--;continue}R({type:"dot",value:p});continue}R({type:"text",value:p})}do if(i=a.pop(),i.type!=="root"){i.nodes.forEach(T=>{T.nodes||(T.type==="open"&&(T.isOpen=!0),T.type==="close"&&(T.isClose=!0),T.nodes||(T.type="text"),T.invalid=!0)});let _=a[a.length-1],x=_.nodes.indexOf(i);_.nodes.splice(x,1,...i.nodes)}while(a.length>0);return R({type:"eos"}),s};Nt.exports=hn});var Gt=K((ds,Dt)=>{"use strict";var Ut=ke(),dn=Ht(),gn=Tt(),mn=Pt(),V=(e,t={})=>{let r=[];if(Array.isArray(e))for(let n of e){let s=V.create(n,t);Array.isArray(s)?r.push(...s):r.push(s)}else r=[].concat(V.create(e,t));return t&&t.expand===!0&&t.nodupes===!0&&(r=[...new Set(r)]),r};V.parse=(e,t={})=>mn(e,t);V.stringify=(e,t={})=>typeof e=="string"?Ut(V.parse(e,t),t):Ut(e,t);V.compile=(e,t={})=>(typeof e=="string"&&(e=V.parse(e,t)),dn(e,t));V.expand=(e,t={})=>{typeof e=="string"&&(e=V.parse(e,t));let r=gn(e,t);return t.noempty===!0&&(r=r.filter(Boolean)),t.nodupes===!0&&(r=[...new Set(r)]),r};V.create=(e,t={})=>e===""||e.length<3?[e]:t.expand!==!0?V.compile(e,t):V.expand(e,t);Dt.exports=V});var ye=K((gs,qt)=>{"use strict";var An=require("path"),ie="\\\\/",Kt=`[^${ie}]`,ce="\\.",Rn="\\+",yn="\\?",Le="\\/",bn="(?=.)",Wt="[^/]",Ke=`(?:${Le}|$)`,jt=`(?:^|${Le})`,We=`${ce}{1,2}${Ke}`,_n=`(?!${ce})`,En=`(?!${jt}${We})`,xn=`(?!${ce}{0,1}${Ke})`,Cn=`(?!${We})`,wn=`[^.${Le}]`,Sn=`${Wt}*?`,Ft={DOT_LITERAL:ce,PLUS_LITERAL:Rn,QMARK_LITERAL:yn,SLASH_LITERAL:Le,ONE_CHAR:bn,QMARK:Wt,END_ANCHOR:Ke,DOTS_SLASH:We,NO_DOT:_n,NO_DOTS:En,NO_DOT_SLASH:xn,NO_DOTS_SLASH:Cn,QMARK_NO_DOT:wn,STAR:Sn,START_ANCHOR:jt},vn=Q(B({},Ft),{SLASH_LITERAL:`[${ie}]`,QMARK:Kt,STAR:`${Kt}*?`,DOTS_SLASH:`${ce}{1,2}(?:[${ie}]|$)`,NO_DOT:`(?!${ce})`,NO_DOTS:`(?!(?:^|[${ie}])${ce}{1,2}(?:[${ie}]|$))`,NO_DOT_SLASH:`(?!${ce}{0,1}(?:[${ie}]|$))`,NO_DOTS_SLASH:`(?!${ce}{1,2}(?:[${ie}]|$))`,QMARK_NO_DOT:`[^.${ie}]`,START_ANCHOR:`(?:^|[${ie}])`,END_ANCHOR:`(?:[${ie}]|$)`}),Hn={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"};qt.exports={MAX_LENGTH:1024*64,POSIX_REGEX_SOURCE:Hn,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:An.sep,extglobChars(e){return{"!":{type:"negate",open:"(?:(?!(?:",close:`))${e.STAR})`},"?":{type:"qmark",open:"(?:",close:")?"},"+":{type:"plus",open:"(?:",close:")+"},"*":{type:"star",open:"(?:",close:")*"},"@":{type:"at",open:"(?:",close:")"}}},globChars(e){return e===!0?vn:Ft}}});var be=K(Z=>{"use strict";var $n=require("path"),kn=process.platform==="win32",{REGEX_BACKSLASH:Tn,REGEX_REMOVE_BACKSLASH:Ln,REGEX_SPECIAL_CHARS:On,REGEX_SPECIAL_CHARS_GLOBAL:Nn}=ye();Z.isObject=e=>e!==null&&typeof e=="object"&&!Array.isArray(e);Z.hasRegexChars=e=>On.test(e);Z.isRegexChar=e=>e.length===1&&Z.hasRegexChars(e);Z.escapeRegex=e=>e.replace(Nn,"\\$1");Z.toPosixSlashes=e=>e.replace(Tn,"/");Z.removeBackslashes=e=>e.replace(Ln,t=>t==="\\"?"":t);Z.supportsLookbehinds=()=>{let e=process.version.slice(1).split(".").map(Number);return e.length===3&&e[0]>=9||e[0]===8&&e[1]>=10};Z.isWindows=e=>e&&typeof e.windows=="boolean"?e.windows:kn===!0||$n.sep==="\\";Z.escapeLast=(e,t,r)=>{let n=e.lastIndexOf(t,r);return n===-1?e:e[n-1]==="\\"?Z.escapeLast(e,t,n-1):`${e.slice(0,n)}\\${e.slice(n)}`};Z.removePrefix=(e,t={})=>{let r=e;return r.startsWith("./")&&(r=r.slice(2),t.prefix="./"),r};Z.wrapOutput=(e,t={},r={})=>{let n=r.contains?"":"^",s=r.contains?"":"$",a=`${n}(?:${e})${s}`;return t.negated===!0&&(a=`(?:^(?!${a}).*$)`),a}});var er=K((As,Qt)=>{"use strict";var Xt=be(),{CHAR_ASTERISK:je,CHAR_AT:In,CHAR_BACKWARD_SLASH:_e,CHAR_COMMA:Bn,CHAR_DOT:Fe,CHAR_EXCLAMATION_MARK:Qe,CHAR_FORWARD_SLASH:Zt,CHAR_LEFT_CURLY_BRACE:Xe,CHAR_LEFT_PARENTHESES:Ze,CHAR_LEFT_SQUARE_BRACKET:Mn,CHAR_PLUS:Pn,CHAR_QUESTION_MARK:Yt,CHAR_RIGHT_CURLY_BRACE:Dn,CHAR_RIGHT_PARENTHESES:zt,CHAR_RIGHT_SQUARE_BRACKET:Un}=ye(),Vt=e=>e===Zt||e===_e,Jt=e=>{e.isPrefix!==!0&&(e.depth=e.isGlobstar?Infinity:1)},Gn=(e,t)=>{let r=t||{},n=e.length-1,s=r.parts===!0||r.scanToEnd===!0,a=[],i=[],o=[],h=e,g=-1,f=0,A=0,p=!1,k=!1,y=!1,R=!1,_=!1,x=!1,T=!1,O=!1,W=!1,G=!1,ne=0,E,b,C={value:"",depth:0,isGlob:!1},M=()=>g>=n,l=()=>h.charCodeAt(g+1),H=()=>(E=b,h.charCodeAt(++g));for(;g0&&(j=h.slice(0,f),h=h.slice(f),A-=f),w&&y===!0&&A>0?(w=h.slice(0,A),c=h.slice(A)):y===!0?(w="",c=h):w=h,w&&w!==""&&w!=="/"&&w!==h&&Vt(w.charCodeAt(w.length-1))&&(w=w.slice(0,-1)),r.unescape===!0&&(c&&(c=Xt.removeBackslashes(c)),w&&T===!0&&(w=Xt.removeBackslashes(w)));let u={prefix:j,input:e,start:f,base:w,glob:c,isBrace:p,isBracket:k,isGlob:y,isExtglob:R,isGlobstar:_,negated:O,negatedExtglob:W};if(r.tokens===!0&&(u.maxDepth=0,Vt(b)||i.push(C),u.tokens=i),r.parts===!0||r.tokens===!0){let I;for(let $=0;${"use strict";var Oe=ye(),J=be(),{MAX_LENGTH:Ne,POSIX_REGEX_SOURCE:qn,REGEX_NON_SPECIAL_CHARS:Kn,REGEX_SPECIAL_CHARS_BACKREF:Wn,REPLACEMENTS:rr}=Oe,jn=(e,t)=>{if(typeof t.expandRange=="function")return t.expandRange(...e,t);e.sort();let r=`[${e.join("-")}]`;try{new RegExp(r)}catch(n){return e.map(s=>J.escapeRegex(s)).join("..")}return r},de=(e,t)=>`Missing ${e}: "${t}" - use "\\\\${t}" to match literal characters`,nr=(e,t)=>{if(typeof e!="string")throw new TypeError("Expected a string");e=rr[e]||e;let r=B({},t),n=typeof r.maxLength=="number"?Math.min(Ne,r.maxLength):Ne,s=e.length;if(s>n)throw new SyntaxError(`Input length: ${s}, exceeds maximum allowed length: ${n}`);let a={type:"bos",value:"",output:r.prepend||""},i=[a],o=r.capture?"":"?:",h=J.isWindows(t),g=Oe.globChars(h),f=Oe.extglobChars(g),{DOT_LITERAL:A,PLUS_LITERAL:p,SLASH_LITERAL:k,ONE_CHAR:y,DOTS_SLASH:R,NO_DOT:_,NO_DOT_SLASH:x,NO_DOTS_SLASH:T,QMARK:O,QMARK_NO_DOT:W,STAR:G,START_ANCHOR:ne}=g,E=m=>`(${o}(?:(?!${ne}${m.dot?R:A}).)*?)`,b=r.dot?"":_,C=r.dot?O:W,M=r.bash===!0?E(r):G;r.capture&&(M=`(${M})`),typeof r.noext=="boolean"&&(r.noextglob=r.noext);let l={input:e,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:i};e=J.removePrefix(e,l),s=e.length;let H=[],w=[],j=[],c=a,u,I=()=>l.index===s-1,$=l.peek=(m=1)=>e[l.index+m],ee=l.advance=()=>e[++l.index]||"",se=()=>e.slice(l.index+1),z=(m="",L=0)=>{l.consumed+=m,l.index+=L},Ce=m=>{l.output+=m.output!=null?m.output:m.value,z(m.value)},xr=()=>{let m=1;for(;$()==="!"&&($(2)!=="("||$(3)==="?");)ee(),l.start++,m++;return m%2==0?!1:(l.negated=!0,l.start++,!0)},we=m=>{l[m]++,j.push(m)},ue=m=>{l[m]--,j.pop()},v=m=>{if(c.type==="globstar"){let L=l.braces>0&&(m.type==="comma"||m.type==="brace"),d=m.extglob===!0||H.length&&(m.type==="pipe"||m.type==="paren");m.type!=="slash"&&m.type!=="paren"&&!L&&!d&&(l.output=l.output.slice(0,-c.output.length),c.type="star",c.value="*",c.output=M,l.output+=c.output)}if(H.length&&m.type!=="paren"&&(H[H.length-1].inner+=m.value),(m.value||m.output)&&Ce(m),c&&c.type==="text"&&m.type==="text"){c.value+=m.value,c.output=(c.output||"")+m.value;return}m.prev=c,i.push(m),c=m},Se=(m,L)=>{let d=Q(B({},f[L]),{conditions:1,inner:""});d.prev=c,d.parens=l.parens,d.output=l.output;let S=(r.capture?"(":"")+d.open;we("parens"),v({type:m,value:L,output:l.output?"":y}),v({type:"paren",extglob:!0,value:ee(),output:S}),H.push(d)},Cr=m=>{let L=m.close+(r.capture?")":""),d;if(m.type==="negate"){let S=M;m.inner&&m.inner.length>1&&m.inner.includes("/")&&(S=E(r)),(S!==M||I()||/^\)+$/.test(se()))&&(L=m.close=`)$))${S}`),m.inner.includes("*")&&(d=se())&&/^\.[^\\/.]+$/.test(d)&&(L=m.close=`)${d})${S})`),m.prev.type==="bos"&&(l.negatedExtglob=!0)}v({type:"paren",extglob:!0,value:u,output:L}),ue("parens")};if(r.fastpaths!==!1&&!/(^[*!]|[/()[\]{}"])/.test(e)){let m=!1,L=e.replace(Wn,(d,S,P,F,q,Me)=>F==="\\"?(m=!0,d):F==="?"?S?S+F+(q?O.repeat(q.length):""):Me===0?C+(q?O.repeat(q.length):""):O.repeat(P.length):F==="."?A.repeat(P.length):F==="*"?S?S+F+(q?M:""):M:S?d:`\\${d}`);return m===!0&&(r.unescape===!0?L=L.replace(/\\/g,""):L=L.replace(/\\+/g,d=>d.length%2==0?"\\\\":d?"\\":"")),L===e&&r.contains===!0?(l.output=e,l):(l.output=J.wrapOutput(L,l,t),l)}for(;!I();){if(u=ee(),u==="\0")continue;if(u==="\\"){let d=$();if(d==="/"&&r.bash!==!0||d==="."||d===";")continue;if(!d){u+="\\",v({type:"text",value:u});continue}let S=/^\\+/.exec(se()),P=0;if(S&&S[0].length>2&&(P=S[0].length,l.index+=P,P%2!=0&&(u+="\\")),r.unescape===!0?u=ee():u+=ee(),l.brackets===0){v({type:"text",value:u});continue}}if(l.brackets>0&&(u!=="]"||c.value==="["||c.value==="[^")){if(r.posix!==!1&&u===":"){let d=c.value.slice(1);if(d.includes("[")&&(c.posix=!0,d.includes(":"))){let S=c.value.lastIndexOf("["),P=c.value.slice(0,S),F=c.value.slice(S+2),q=qn[F];if(q){c.value=P+q,l.backtrack=!0,ee(),!a.output&&i.indexOf(c)===1&&(a.output=y);continue}}}(u==="["&&$()!==":"||u==="-"&&$()==="]")&&(u=`\\${u}`),u==="]"&&(c.value==="["||c.value==="[^")&&(u=`\\${u}`),r.posix===!0&&u==="!"&&c.value==="["&&(u="^"),c.value+=u,Ce({value:u});continue}if(l.quotes===1&&u!=='"'){u=J.escapeRegex(u),c.value+=u,Ce({value:u});continue}if(u==='"'){l.quotes=l.quotes===1?0:1,r.keepQuotes===!0&&v({type:"text",value:u});continue}if(u==="("){we("parens"),v({type:"paren",value:u});continue}if(u===")"){if(l.parens===0&&r.strictBrackets===!0)throw new SyntaxError(de("opening","("));let d=H[H.length-1];if(d&&l.parens===d.parens+1){Cr(H.pop());continue}v({type:"paren",value:u,output:l.parens?")":"\\)"}),ue("parens");continue}if(u==="["){if(r.nobracket===!0||!se().includes("]")){if(r.nobracket!==!0&&r.strictBrackets===!0)throw new SyntaxError(de("closing","]"));u=`\\${u}`}else we("brackets");v({type:"bracket",value:u});continue}if(u==="]"){if(r.nobracket===!0||c&&c.type==="bracket"&&c.value.length===1){v({type:"text",value:u,output:`\\${u}`});continue}if(l.brackets===0){if(r.strictBrackets===!0)throw new SyntaxError(de("opening","["));v({type:"text",value:u,output:`\\${u}`});continue}ue("brackets");let d=c.value.slice(1);if(c.posix!==!0&&d[0]==="^"&&!d.includes("/")&&(u=`/${u}`),c.value+=u,Ce({value:u}),r.literalBrackets===!1||J.hasRegexChars(d))continue;let S=J.escapeRegex(c.value);if(l.output=l.output.slice(0,-c.value.length),r.literalBrackets===!0){l.output+=S,c.value=S;continue}c.value=`(${o}${S}|${c.value})`,l.output+=c.value;continue}if(u==="{"&&r.nobrace!==!0){we("braces");let d={type:"brace",value:u,output:"(",outputIndex:l.output.length,tokensIndex:l.tokens.length};w.push(d),v(d);continue}if(u==="}"){let d=w[w.length-1];if(r.nobrace===!0||!d){v({type:"text",value:u,output:u});continue}let S=")";if(d.dots===!0){let P=i.slice(),F=[];for(let q=P.length-1;q>=0&&(i.pop(),P[q].type!=="brace");q--)P[q].type!=="dots"&&F.unshift(P[q].value);S=jn(F,r),l.backtrack=!0}if(d.comma!==!0&&d.dots!==!0){let P=l.output.slice(0,d.outputIndex),F=l.tokens.slice(d.tokensIndex);d.value=d.output="\\{",u=S="\\}",l.output=P;for(let q of F)l.output+=q.output||q.value}v({type:"brace",value:u,output:S}),ue("braces"),w.pop();continue}if(u==="|"){H.length>0&&H[H.length-1].conditions++,v({type:"text",value:u});continue}if(u===","){let d=u,S=w[w.length-1];S&&j[j.length-1]==="braces"&&(S.comma=!0,d="|"),v({type:"comma",value:u,output:d});continue}if(u==="/"){if(c.type==="dot"&&l.index===l.start+1){l.start=l.index+1,l.consumed="",l.output="",i.pop(),c=a;continue}v({type:"slash",value:u,output:k});continue}if(u==="."){if(l.braces>0&&c.type==="dot"){c.value==="."&&(c.output=A);let d=w[w.length-1];c.type="dots",c.output+=u,c.value+=u,d.dots=!0;continue}if(l.braces+l.parens===0&&c.type!=="bos"&&c.type!=="slash"){v({type:"text",value:u,output:A});continue}v({type:"dot",value:u,output:A});continue}if(u==="?"){if(!(c&&c.value==="(")&&r.noextglob!==!0&&$()==="("&&$(2)!=="?"){Se("qmark",u);continue}if(c&&c.type==="paren"){let S=$(),P=u;if(S==="<"&&!J.supportsLookbehinds())throw new Error("Node.js v10 or higher is required for regex lookbehinds");(c.value==="("&&!/[!=<:]/.test(S)||S==="<"&&!/<([!=]|\w+>)/.test(se()))&&(P=`\\${u}`),v({type:"text",value:u,output:P});continue}if(r.dot!==!0&&(c.type==="slash"||c.type==="bos")){v({type:"qmark",value:u,output:W});continue}v({type:"qmark",value:u,output:O});continue}if(u==="!"){if(r.noextglob!==!0&&$()==="("&&($(2)!=="?"||!/[!=<:]/.test($(3)))){Se("negate",u);continue}if(r.nonegate!==!0&&l.index===0){xr();continue}}if(u==="+"){if(r.noextglob!==!0&&$()==="("&&$(2)!=="?"){Se("plus",u);continue}if(c&&c.value==="("||r.regex===!1){v({type:"plus",value:u,output:p});continue}if(c&&(c.type==="bracket"||c.type==="paren"||c.type==="brace")||l.parens>0){v({type:"plus",value:u});continue}v({type:"plus",value:p});continue}if(u==="@"){if(r.noextglob!==!0&&$()==="("&&$(2)!=="?"){v({type:"at",extglob:!0,value:u,output:""});continue}v({type:"text",value:u});continue}if(u!=="*"){(u==="$"||u==="^")&&(u=`\\${u}`);let d=Kn.exec(se());d&&(u+=d[0],l.index+=d[0].length),v({type:"text",value:u});continue}if(c&&(c.type==="globstar"||c.star===!0)){c.type="star",c.star=!0,c.value+=u,c.output=M,l.backtrack=!0,l.globstar=!0,z(u);continue}let m=se();if(r.noextglob!==!0&&/^\([^?]/.test(m)){Se("star",u);continue}if(c.type==="star"){if(r.noglobstar===!0){z(u);continue}let d=c.prev,S=d.prev,P=d.type==="slash"||d.type==="bos",F=S&&(S.type==="star"||S.type==="globstar");if(r.bash===!0&&(!P||m[0]&&m[0]!=="/")){v({type:"star",value:u,output:""});continue}let q=l.braces>0&&(d.type==="comma"||d.type==="brace"),Me=H.length&&(d.type==="pipe"||d.type==="paren");if(!P&&d.type!=="paren"&&!q&&!Me){v({type:"star",value:u,output:""});continue}for(;m.slice(0,3)==="/**";){let ve=e[l.index+4];if(ve&&ve!=="/")break;m=m.slice(3),z("/**",3)}if(d.type==="bos"&&I()){c.type="globstar",c.value+=u,c.output=E(r),l.output=c.output,l.globstar=!0,z(u);continue}if(d.type==="slash"&&d.prev.type!=="bos"&&!F&&I()){l.output=l.output.slice(0,-(d.output+c.output).length),d.output=`(?:${d.output}`,c.type="globstar",c.output=E(r)+(r.strictSlashes?")":"|$)"),c.value+=u,l.globstar=!0,l.output+=d.output+c.output,z(u);continue}if(d.type==="slash"&&d.prev.type!=="bos"&&m[0]==="/"){let ve=m[1]!==void 0?"|$":"";l.output=l.output.slice(0,-(d.output+c.output).length),d.output=`(?:${d.output}`,c.type="globstar",c.output=`${E(r)}${k}|${k}${ve})`,c.value+=u,l.output+=d.output+c.output,l.globstar=!0,z(u+ee()),v({type:"slash",value:"/",output:""});continue}if(d.type==="bos"&&m[0]==="/"){c.type="globstar",c.value+=u,c.output=`(?:^|${k}|${E(r)}${k})`,l.output=c.output,l.globstar=!0,z(u+ee()),v({type:"slash",value:"/",output:""});continue}l.output=l.output.slice(0,-c.output.length),c.type="globstar",c.output=E(r),c.value+=u,l.output+=c.output,l.globstar=!0,z(u);continue}let L={type:"star",value:u,output:M};if(r.bash===!0){L.output=".*?",(c.type==="bos"||c.type==="slash")&&(L.output=b+L.output),v(L);continue}if(c&&(c.type==="bracket"||c.type==="paren")&&r.regex===!0){L.output=u,v(L);continue}(l.index===l.start||c.type==="slash"||c.type==="dot")&&(c.type==="dot"?(l.output+=x,c.output+=x):r.dot===!0?(l.output+=T,c.output+=T):(l.output+=b,c.output+=b),$()!=="*"&&(l.output+=y,c.output+=y)),v(L)}for(;l.brackets>0;){if(r.strictBrackets===!0)throw new SyntaxError(de("closing","]"));l.output=J.escapeLast(l.output,"["),ue("brackets")}for(;l.parens>0;){if(r.strictBrackets===!0)throw new SyntaxError(de("closing",")"));l.output=J.escapeLast(l.output,"("),ue("parens")}for(;l.braces>0;){if(r.strictBrackets===!0)throw new SyntaxError(de("closing","}"));l.output=J.escapeLast(l.output,"{"),ue("braces")}if(r.strictSlashes!==!0&&(c.type==="star"||c.type==="bracket")&&v({type:"maybe_slash",value:"",output:`${k}?`}),l.backtrack===!0){l.output="";for(let m of l.tokens)l.output+=m.output!=null?m.output:m.value,m.suffix&&(l.output+=m.suffix)}return l};nr.fastpaths=(e,t)=>{let r=B({},t),n=typeof r.maxLength=="number"?Math.min(Ne,r.maxLength):Ne,s=e.length;if(s>n)throw new SyntaxError(`Input length: ${s}, exceeds maximum allowed length: ${n}`);e=rr[e]||e;let a=J.isWindows(t),{DOT_LITERAL:i,SLASH_LITERAL:o,ONE_CHAR:h,DOTS_SLASH:g,NO_DOT:f,NO_DOTS:A,NO_DOTS_SLASH:p,STAR:k,START_ANCHOR:y}=Oe.globChars(a),R=r.dot?A:f,_=r.dot?p:f,x=r.capture?"":"?:",T={negated:!1,prefix:""},O=r.bash===!0?".*?":k;r.capture&&(O=`(${O})`);let W=b=>b.noglobstar===!0?O:`(${x}(?:(?!${y}${b.dot?g:i}).)*?)`,G=b=>{switch(b){case"*":return`${R}${h}${O}`;case".*":return`${i}${h}${O}`;case"*.*":return`${R}${O}${i}${h}${O}`;case"*/*":return`${R}${O}${o}${h}${_}${O}`;case"**":return R+W(r);case"**/*":return`(?:${R}${W(r)}${o})?${_}${h}${O}`;case"**/*.*":return`(?:${R}${W(r)}${o})?${_}${O}${i}${h}${O}`;case"**/.*":return`(?:${R}${W(r)}${o})?${i}${h}${O}`;default:{let C=/^(.*?)\.(\w+)$/.exec(b);if(!C)return;let M=G(C[1]);return M?M+i+C[2]:void 0}}},ne=J.removePrefix(e,T),E=G(ne);return E&&r.strictSlashes!==!0&&(E+=`${o}?`),E};tr.exports=nr});var ir=K((ys,ar)=>{"use strict";var Fn=require("path"),Qn=er(),Ye=sr(),ze=be(),Xn=ye(),Zn=e=>e&&typeof e=="object"&&!Array.isArray(e),D=(e,t,r=!1)=>{if(Array.isArray(e)){let f=e.map(p=>D(p,t,r));return p=>{for(let k of f){let y=k(p);if(y)return y}return!1}}let n=Zn(e)&&e.tokens&&e.input;if(e===""||typeof e!="string"&&!n)throw new TypeError("Expected pattern to be a non-empty string");let s=t||{},a=ze.isWindows(t),i=n?D.compileRe(e,t):D.makeRe(e,t,!1,!0),o=i.state;delete i.state;let h=()=>!1;if(s.ignore){let f=Q(B({},t),{ignore:null,onMatch:null,onResult:null});h=D(s.ignore,f,r)}let g=(f,A=!1)=>{let{isMatch:p,match:k,output:y}=D.test(f,i,t,{glob:e,posix:a}),R={glob:e,state:o,regex:i,posix:a,input:f,output:y,match:k,isMatch:p};return typeof s.onResult=="function"&&s.onResult(R),p===!1?(R.isMatch=!1,A?R:!1):h(f)?(typeof s.onIgnore=="function"&&s.onIgnore(R),R.isMatch=!1,A?R:!1):(typeof s.onMatch=="function"&&s.onMatch(R),A?R:!0)};return r&&(g.state=o),g};D.test=(e,t,r,{glob:n,posix:s}={})=>{if(typeof e!="string")throw new TypeError("Expected input to be a string");if(e==="")return{isMatch:!1,output:""};let a=r||{},i=a.format||(s?ze.toPosixSlashes:null),o=e===n,h=o&&i?i(e):e;return o===!1&&(h=i?i(e):e,o=h===n),(o===!1||a.capture===!0)&&(a.matchBase===!0||a.basename===!0?o=D.matchBase(e,t,r,s):o=t.exec(h)),{isMatch:Boolean(o),match:o,output:h}};D.matchBase=(e,t,r,n=ze.isWindows(r))=>(t instanceof RegExp?t:D.makeRe(t,r)).test(Fn.basename(e));D.isMatch=(e,t,r)=>D(t,r)(e);D.parse=(e,t)=>Array.isArray(e)?e.map(r=>D.parse(r,t)):Ye(e,Q(B({},t),{fastpaths:!1}));D.scan=(e,t)=>Qn(e,t);D.compileRe=(e,t,r=!1,n=!1)=>{if(r===!0)return e.output;let s=t||{},a=s.contains?"":"^",i=s.contains?"":"$",o=`${a}(?:${e.output})${i}`;e&&e.negated===!0&&(o=`^(?!${o}).*$`);let h=D.toRegex(o,t);return n===!0&&(h.state=e),h};D.makeRe=(e,t={},r=!1,n=!1)=>{if(!e||typeof e!="string")throw new TypeError("Expected a non-empty string");let s={negated:!1,fastpaths:!0};return t.fastpaths!==!1&&(e[0]==="."||e[0]==="*")&&(s.output=Ye.fastpaths(e,t)),s.output||(s=Ye(e,t)),D.compileRe(s,t,r,n)};D.toRegex=(e,t)=>{try{let r=t||{};return new RegExp(e,r.flags||(r.nocase?"i":""))}catch(r){if(t&&t.debug===!0)throw r;return/$^/}};D.constants=Xn;ar.exports=D});var cr=K((bs,or)=>{"use strict";or.exports=ir()});var hr=K((_s,ur)=>{"use strict";var lr=require("util"),pr=Gt(),oe=cr(),Ve=be(),fr=e=>e===""||e==="./",N=(e,t,r)=>{t=[].concat(t),e=[].concat(e);let n=new Set,s=new Set,a=new Set,i=0,o=f=>{a.add(f.output),r&&r.onResult&&r.onResult(f)};for(let f=0;f!n.has(f));if(r&&g.length===0){if(r.failglob===!0)throw new Error(`No matches found for "${t.join(", ")}"`);if(r.nonull===!0||r.nullglob===!0)return r.unescape?t.map(f=>f.replace(/\\/g,"")):t}return g};N.match=N;N.matcher=(e,t)=>oe(e,t);N.isMatch=(e,t,r)=>oe(t,r)(e);N.any=N.isMatch;N.not=(e,t,r={})=>{t=[].concat(t).map(String);let n=new Set,s=[],a=o=>{r.onResult&&r.onResult(o),s.push(o.output)},i=N(e,t,Q(B({},r),{onResult:a}));for(let o of s)i.includes(o)||n.add(o);return[...n]};N.contains=(e,t,r)=>{if(typeof e!="string")throw new TypeError(`Expected a string: "${lr.inspect(e)}"`);if(Array.isArray(t))return t.some(n=>N.contains(e,n,r));if(typeof t=="string"){if(fr(e)||fr(t))return!1;if(e.includes(t)||e.startsWith("./")&&e.slice(2).includes(t))return!0}return N.isMatch(e,t,Q(B({},r),{contains:!0}))};N.matchKeys=(e,t,r)=>{if(!Ve.isObject(e))throw new TypeError("Expected the first argument to be an object");let n=N(Object.keys(e),t,r),s={};for(let a of n)s[a]=e[a];return s};N.some=(e,t,r)=>{let n=[].concat(e);for(let s of[].concat(t)){let a=oe(String(s),r);if(n.some(i=>a(i)))return!0}return!1};N.every=(e,t,r)=>{let n=[].concat(e);for(let s of[].concat(t)){let a=oe(String(s),r);if(!n.every(i=>a(i)))return!1}return!0};N.all=(e,t,r)=>{if(typeof e!="string")throw new TypeError(`Expected a string: "${lr.inspect(e)}"`);return[].concat(t).every(n=>oe(n,r)(e))};N.capture=(e,t,r)=>{let n=Ve.isWindows(r),a=oe.makeRe(String(e),Q(B({},r),{capture:!0})).exec(n?Ve.toPosixSlashes(t):t);if(a)return a.slice(1).map(i=>i===void 0?"":i)};N.makeRe=(...e)=>oe.makeRe(...e);N.scan=(...e)=>oe.scan(...e);N.parse=(e,t)=>{let r=[];for(let n of[].concat(e||[]))for(let s of pr(String(n),t))r.push(oe.parse(s,t));return r};N.braces=(e,t)=>{if(typeof e!="string")throw new TypeError("Expected a string");return t&&t.nobrace===!0||!/\{.*\}/.test(e)?[e]:pr(e,t)};N.braceExpand=(e,t)=>{if(typeof e!="string")throw new TypeError("Expected a string");return N.braces(e,Q(B({},t),{expand:!0}))};ur.exports=N});var gr=K((Es,dr)=>{"use strict";dr.exports=(e,...t)=>new Promise(r=>{r(e(...t))})});var Ar=K((xs,Je)=>{"use strict";var Yn=gr(),mr=e=>{if(e<1)throw new TypeError("Expected `concurrency` to be a number from 1 and up");let t=[],r=0,n=()=>{r--,t.length>0&&t.shift()()},s=(o,h,...g)=>{r++;let f=Yn(o,...g);h(f),f.then(n,n)},a=(o,h,...g)=>{rnew Promise(g=>a(o,g,...h));return Object.defineProperties(i,{activeCount:{get:()=>r},pendingCount:{get:()=>t.length}}),i};Je.exports=mr;Je.exports.default=mr});var Vn={};Or(Vn,{default:()=>es});var He=X(require("@yarnpkg/cli")),ae=X(require("@yarnpkg/core")),nt=X(require("@yarnpkg/core")),le=X(require("clipanion")),Ae=class extends He.BaseCommand{constructor(){super(...arguments);this.json=le.Option.Boolean("--json",!1,{description:"Format the output as an NDJSON stream"});this.production=le.Option.Boolean("--production",!1,{description:"Only install regular dependencies by omitting dev dependencies"});this.all=le.Option.Boolean("-A,--all",!1,{description:"Install the entire project"});this.workspaces=le.Option.Rest()}async execute(){let t=await ae.Configuration.find(this.context.cwd,this.context.plugins),{project:r,workspace:n}=await ae.Project.find(t,this.context.cwd),s=await ae.Cache.find(t);await r.restoreInstallState({restoreResolutions:!1});let a;if(this.all)a=new Set(r.workspaces);else if(this.workspaces.length===0){if(!n)throw new He.WorkspaceRequiredError(r.cwd,this.context.cwd);a=new Set([n])}else a=new Set(this.workspaces.map(o=>r.getWorkspaceByIdent(nt.structUtils.parseIdent(o))));for(let o of a)for(let h of this.production?["dependencies"]:ae.Manifest.hardDependencies)for(let g of o.manifest.getForScope(h).values()){let f=r.tryWorkspaceByDescriptor(g);f!==null&&a.add(f)}for(let o of r.workspaces)a.has(o)?this.production&&o.manifest.devDependencies.clear():(o.manifest.installConfig=o.manifest.installConfig||{},o.manifest.installConfig.selfReferences=!1,o.manifest.dependencies.clear(),o.manifest.devDependencies.clear(),o.manifest.peerDependencies.clear(),o.manifest.scripts.clear());return(await ae.StreamReport.start({configuration:t,json:this.json,stdout:this.context.stdout,includeLogs:!0},async o=>{await r.install({cache:s,report:o,persistProject:!1})})).exitCode()}};Ae.paths=[["workspaces","focus"]],Ae.usage=le.Command.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 "});var st=Ae;var Ie=X(require("@yarnpkg/cli")),ge=X(require("@yarnpkg/core")),Ee=X(require("@yarnpkg/core")),Y=X(require("@yarnpkg/core")),Rr=X(require("@yarnpkg/plugin-git")),U=X(require("clipanion")),Be=X(hr()),yr=X(require("os")),br=X(Ar()),re=X(require("typanion")),xe=class extends Ie.BaseCommand{constructor(){super(...arguments);this.recursive=U.Option.Boolean("-R,--recursive",!1,{description:"Find packages via dependencies/devDependencies instead of using the workspaces field"});this.from=U.Option.Array("--from",[],{description:"An array of glob pattern idents from which to base any recursion"});this.all=U.Option.Boolean("-A,--all",!1,{description:"Run the command on all workspaces of a project"});this.verbose=U.Option.Boolean("-v,--verbose",!1,{description:"Prefix each output line with the name of the originating workspace"});this.parallel=U.Option.Boolean("-p,--parallel",!1,{description:"Run the commands in parallel"});this.interlaced=U.Option.Boolean("-i,--interlaced",!1,{description:"Print the output of commands in real-time instead of buffering it"});this.jobs=U.Option.String("-j,--jobs",{description:"The maximum number of parallel tasks that the execution will be limited to; or `unlimited`",validator:re.isOneOf([re.isEnum(["unlimited"]),re.applyCascade(re.isNumber(),[re.isInteger(),re.isAtLeast(1)])])});this.topological=U.Option.Boolean("-t,--topological",!1,{description:"Run the command after all workspaces it depends on (regular) have finished"});this.topologicalDev=U.Option.Boolean("--topological-dev",!1,{description:"Run the command after all workspaces it depends on (regular + dev) have finished"});this.include=U.Option.Array("--include",[],{description:"An array of glob pattern idents; only matching workspaces will be traversed"});this.exclude=U.Option.Array("--exclude",[],{description:"An array of glob pattern idents; matching workspaces won't be traversed"});this.publicOnly=U.Option.Boolean("--no-private",{description:"Avoid running the command on private workspaces"});this.since=U.Option.String("--since",{description:"Only include workspaces that have been changed since the specified ref.",tolerateBoolean:!0});this.commandName=U.Option.String();this.args=U.Option.Proxy()}async execute(){let t=await ge.Configuration.find(this.context.cwd,this.context.plugins),{project:r,workspace:n}=await ge.Project.find(t,this.context.cwd);if(!this.all&&!n)throw new Ie.WorkspaceRequiredError(r.cwd,this.context.cwd);await r.restoreInstallState();let s=this.cli.process([this.commandName,...this.args]),a=s.path.length===1&&s.path[0]==="run"&&typeof s.scriptName!="undefined"?s.scriptName:null;if(s.path.length===0)throw new U.UsageError("Invalid subcommand name for iteration - use the 'run' keyword if you wish to execute a script");let i=this.all?r.topLevelWorkspace:n,o=this.since?Array.from(await Rr.gitUtils.fetchChangedWorkspaces({ref:this.since,project:r})):[i,...this.from.length>0?i.getRecursiveWorkspaceChildren():[]],h=E=>Be.default.isMatch(Y.structUtils.stringifyIdent(E.locator),this.from),g=this.from.length>0?o.filter(h):o,f=new Set([...g,...g.map(E=>[...this.recursive?this.since?E.getRecursiveWorkspaceDependents():E.getRecursiveWorkspaceDependencies():E.getRecursiveWorkspaceChildren()]).flat()]),A=[],p=!1;if(a==null?void 0:a.includes(":")){for(let E of r.workspaces)if(E.manifest.scripts.has(a)&&(p=!p,p===!1))break}for(let E of f)a&&!E.manifest.scripts.has(a)&&!p&&!(await ge.scriptUtils.getWorkspaceAccessibleBinaries(E)).has(a)||a===process.env.npm_lifecycle_event&&E.cwd===n.cwd||this.include.length>0&&!Be.default.isMatch(Y.structUtils.stringifyIdent(E.locator),this.include)||this.exclude.length>0&&Be.default.isMatch(Y.structUtils.stringifyIdent(E.locator),this.exclude)||this.publicOnly&&E.manifest.private===!0||A.push(E);let k=this.parallel?this.jobs==="unlimited"?Infinity:this.jobs||Math.max(1,(0,yr.cpus)().length/2):1,y=k===1?!1:this.parallel,R=y?this.interlaced:!0,_=(0,br.default)(k),x=new Map,T=new Set,O=0,W=null,G=!1,ne=await Ee.StreamReport.start({configuration:t,stdout:this.context.stdout},async E=>{let b=async(C,{commandIndex:M})=>{if(G)return-1;!y&&this.verbose&&M>1&&E.reportSeparator();let l=zn(C,{configuration:t,verbose:this.verbose,commandIndex:M}),[H,w]=_r(E,{prefix:l,interlaced:R}),[j,c]=_r(E,{prefix:l,interlaced:R});try{this.verbose&&E.reportInfo(null,`${l} Process started`);let u=Date.now(),I=await this.cli.run([this.commandName,...this.args],{cwd:C.cwd,stdout:H,stderr:j})||0;H.end(),j.end(),await w,await c;let $=Date.now();if(this.verbose){let ee=t.get("enableTimers")?`, completed in ${Y.formatUtils.pretty(t,$-u,Y.formatUtils.Type.DURATION)}`:"";E.reportInfo(null,`${l} Process exited (exit code ${I})${ee}`)}return I===130&&(G=!0,W=I),I}catch(u){throw H.end(),j.end(),await w,await c,u}};for(let C of A)x.set(C.anchoredLocator.locatorHash,C);for(;x.size>0&&!E.hasErrors();){let C=[];for(let[H,w]of x){if(T.has(w.anchoredDescriptor.descriptorHash))continue;let j=!0;if(this.topological||this.topologicalDev){let c=this.topologicalDev?new Map([...w.manifest.dependencies,...w.manifest.devDependencies]):w.manifest.dependencies;for(let u of c.values()){let I=r.tryWorkspaceByDescriptor(u);if(j=I===null||!x.has(I.anchoredLocator.locatorHash),!j)break}}if(!!j&&(T.add(w.anchoredDescriptor.descriptorHash),C.push(_(async()=>{let c=await b(w,{commandIndex:++O});return x.delete(H),T.delete(w.anchoredDescriptor.descriptorHash),c})),!y))break}if(C.length===0){let H=Array.from(x.values()).map(w=>Y.structUtils.prettyLocator(t,w.anchoredLocator)).join(", ");E.reportError(Ee.MessageName.CYCLIC_DEPENDENCIES,`Dependency cycle detected (${H})`);return}let l=(await Promise.all(C)).find(H=>H!==0);W===null&&(W=typeof l!="undefined"?1:W),(this.topological||this.topologicalDev)&&typeof l!="undefined"&&E.reportError(Ee.MessageName.UNNAMED,"The command failed for workspaces that are depended upon by other workspaces; can't satisfy the dependency graph")}});return W!==null?W:ne.exitCode()}};xe.paths=[["workspaces","foreach"]],xe.usage=U.Command.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. By default yarn runs the command only on current and all its descendant workspaces.\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 `--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 - 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.\n\n Adding the `-v,--verbose` flag will cause Yarn to print more information; in particular the name of the workspace that generated the output will be printed at the front of each line.\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 current and all descendant packages","yarn workspaces foreach npm publish --tolerate-republish"],["Run build script on current and all descendant packages","yarn workspaces foreach run build"],["Run build script on current and all descendant packages in parallel, building package dependencies first","yarn workspaces foreach -pt run build"],["Run build script on several packages and all their dependencies, building dependencies first","yarn workspaces foreach -ptR --from '{workspace-a,workspace-b}' run build"]]});var Er=xe;function _r(e,{prefix:t,interlaced:r}){let n=e.createStreamReporter(t),s=new Y.miscUtils.DefaultStream;s.pipe(n,{end:!1}),s.on("finish",()=>{n.end()});let a=new Promise(o=>{n.on("finish",()=>{o(s.active)})});if(r)return[s,a];let i=new Y.miscUtils.BufferStream;return i.pipe(s,{end:!1}),i.on("finish",()=>{s.end()}),[i,a]}function zn(e,{configuration:t,commandIndex:r,verbose:n}){if(!n)return null;let s=Y.structUtils.convertToIdent(e.locator),i=`[${Y.structUtils.stringifyIdent(s)}]:`,o=["#2E86AB","#A23B72","#F18F01","#C73E1D","#CCE2A3"],h=o[r%o.length];return Y.formatUtils.pretty(t,i,h)}var Jn={commands:[st,Er]},es=Jn;return Vn;})(); +/*! + * fill-range + * + * Copyright (c) 2014-present, Jon Schlinkert. + * Licensed under the MIT License. + */ +/*! + * is-number + * + * Copyright (c) 2014-present, Jon Schlinkert. + * Released under the MIT License. + */ +/*! + * to-regex-range + * + * Copyright (c) 2015-present, Jon Schlinkert. + * Released under the MIT License. + */ +return plugin; +} +}; diff --git a/.yarnrc.yml b/.yarnrc.yml index d9f8454..63ccd18 100644 --- a/.yarnrc.yml +++ b/.yarnrc.yml @@ -1,3 +1,7 @@ nodeLinker: node-modules +plugins: + - path: .yarn/plugins/@yarnpkg/plugin-workspace-tools.cjs + spec: "@yarnpkg/plugin-workspace-tools" + yarnPath: .yarn/releases/yarn-3.2.1.cjs diff --git a/CODEOWNERS b/CODEOWNERS index df75a05..3f1a43f 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -1,5 +1,5 @@ # CODEOWNERS -# The @ardriveapp/services team is the code owner for the entire repository. +# The @ar-io/services team is the code owner for the entire repository. * @ardriveapp/services diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index 3bb06e3..568af69 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -43,7 +43,7 @@ In case of any issues or concerns related to reporting, enforcement, or implemen ## Attribution -This Code of Conduct is adapted from the Contributor Covenant, version 2.0, available at https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. +This Code of Conduct is adapted from the Contributor Covenant, version 2.0, available at . ## Scope diff --git a/Dockerfile b/Dockerfile index 409339a..5f2185a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -ARG NODE_VERSION=18.16.0 +ARG NODE_VERSION=18.17.0 ARG NODE_VERSION_SHORT=18 FROM node:${NODE_VERSION}-bullseye-slim AS builder @@ -8,8 +8,11 @@ WORKDIR /usr/src/app COPY . . RUN yarn && yarn build +# Clean out dependencies +RUN yarn workspaces focus --production + # Extract dist -FROM gcr.io/distroless/nodejs:${NODE_VERSION_SHORT} +FROM gcr.io/distroless/nodejs${NODE_VERSION_SHORT}-debian11 WORKDIR /usr/src/app # Add shell diff --git a/docker-compose.yml b/docker-compose.yml index 48c5d18..aa9fb9d 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -5,7 +5,7 @@ services: build: context: . args: - NODE_VERSION: ${NODE_VERSION:-18.16.0} + NODE_VERSION: ${NODE_VERSION:-18.17.0} NODE_VERSION_SHORT: ${NODE_VERSION_SHORT:-18} environment: NODE_ENV: ${NODE_ENV:-test} @@ -16,6 +16,7 @@ services: DISABLE_LOGS: ${DISABLE_LOGS:-false} STRIPE_SECRET_KEY: ${STRIPE_SECRET_KEY} STRIPE_WEBHOOK_SECRET: ${STRIPE_WEBHOOK_SECRET} + MIGRATE_ON_STARTUP: ${MIGRATE_ON_STARTUP:-} ports: - "${PORT:-3000}:${PORT:-3000}" depends_on: @@ -37,7 +38,7 @@ services: context: . dockerfile: tests/Dockerfile.integration args: - NODE_VERSION: ${NODE_VERSION:-18.16.0} + NODE_VERSION: ${NODE_VERSION:-18.17.0} environment: DB_HOST: payment-service-pg DB_PASSWORD: postgres diff --git a/docs/openapi.yaml b/docs/openapi.yaml index 5b81bc5..041c35e 100644 --- a/docs/openapi.yaml +++ b/docs/openapi.yaml @@ -18,12 +18,39 @@ components: type: object properties: winc: - type: string - description: A big number string representing an amount of credits in winc. One credit is equivalent to 1,000,000,000,000 winc - example: "332824926" + "$ref": "#/components/schemas/Winc" adjustments: "$ref": "#/components/schemas/Adjustment" + PaymentCreditResponse: + type: object + properties: + winc: + "$ref": "#/components/schemas/Winc" + adjustments: + "$ref": "#/components/schemas/Adjustment" + fees: + "$ref": "#/components/schemas/Adjustment" + actualPaymentAmount: + "$ref": "#/components/schemas/ActualPaymentAmount" + quotedPaymentAmount: + "$ref": "#/components/schemas/QuotedPaymentAmount" + + ActualPaymentAmount: + type: integer + description: The actual payment amount a user will spend in the given currency's smallest unit value. For example, $8 USD is 800 + example: 800 + + QuotedPaymentAmount: + type: integer + description: The quoted payment amount without adjustments in the given currency's smallest unit value. For example, $10 USD is 1000 + example: 1000 + + Winc: + type: string + description: A big number string representing an amount of credits in winc. One credit is equivalent to 10^12 winc + example: "332824926" + Adjustment: type: array items: @@ -33,18 +60,21 @@ components: type: string description: type: string - value: + operatorMagnitude: type: number operator: type: string adjustmentAmount: type: string + promoCode: + type: string example: - name: "Adjustment" description: "Some great subsidy" - value: "0.6" + operatorMagnitude: "0.6" operator: "multiply" adjustmentAmount: "-12300" + promoCode: "SOME-GREAT-CODE" CurrencyType: type: string @@ -68,6 +98,10 @@ components: type: string description: The "modulus" of the JWK used to create the signature header + PromoCode: + type: string + description: Comma-separated list of promo codes + TopUpQuote: type: object description: A copy of the top up quote that was saved to the service's database @@ -82,8 +116,9 @@ components: type: string example: arweave paymentAmount: - type: integer - example: 1000 + "$ref": "#/components/schemas/ActualPaymentAmount" + quotedPaymentAmount: + "$ref": "#/components/schemas/QuotedPaymentAmount" currencyType: type: string example: usd @@ -102,7 +137,7 @@ components: properties: winc: type: string - description: A big number representing the amount of credits in winc that 1GB of storage costs based on current market rates. One credit is equivalent to 1,000,000,000,000 winc. + description: A big number representing the amount of credits in winc that 1GB of storage costs based on current market rates. One credit is equivalent to 10^12 winc. example: 332824926 fiat: type: object @@ -185,24 +220,56 @@ paths: description: Returns the current amount of winc this service will quote for a given payment type and amount parameters: + - name: x-signature + in: header + required: false + schema: + "$ref": "#/components/schemas/SignatureHeader" + + - name: x-nonce + in: header + required: false + schema: + "$ref": "#/components/schemas/NonceHeader" + + - name: x-public-key + in: header + required: false + schema: + "$ref": "#/components/schemas/PublicKeyHeader" + - name: type in: path required: true schema: "$ref": "#/components/schemas/CurrencyType" + - name: amount in: path required: true schema: "$ref": "#/components/schemas/PaymentAmount" + - name: promoCode + in: query + required: false + schema: + "$ref": "#/components/schemas/PromoCode" + + - name: destinationAddress + in: query + required: false + schema: + type: string + description: Destination wallet address. This will override the signature header's public key if provided + responses: "200": description: OK content: application/json: schema: - "$ref": "#/components/schemas/CreditResponse" + "$ref": "#/components/schemas/PaymentCreditResponse" "400": description: Bad Request @@ -284,6 +351,24 @@ paths: description: Get a top up quote and payment session for a given method (payment-intent or checkout-session), destination address, currency type, and payment amount parameters: + - name: x-signature + in: header + required: false + schema: + "$ref": "#/components/schemas/SignatureHeader" + + - name: x-nonce + in: header + required: false + schema: + "$ref": "#/components/schemas/NonceHeader" + + - name: x-public-key + in: header + required: false + schema: + "$ref": "#/components/schemas/PublicKeyHeader" + - name: method in: path required: true @@ -310,6 +395,12 @@ paths: schema: "$ref": "#/components/schemas/PaymentAmount" + - name: promoCode + in: query + required: false + schema: + "$ref": "#/components/schemas/PromoCode" + responses: "200": description: OK @@ -324,6 +415,10 @@ paths: topUpQuote: allOf: - "$ref": "#/components/schemas/TopUpQuote" + adjustments: + "$ref": "#/components/schemas/Adjustment" + fees: + "$ref": "#/components/schemas/Adjustment" "403": description: Forbidden @@ -374,14 +469,14 @@ paths: { usd: { - minimumPaymentAmount: 1000, + minimumPaymentAmount: 500, maximumPaymentAmount: 1000000, suggestedPaymentAmounts: [2500, 5000, 10000], zeroDecimalCurrency: false, }, jpy: { - minimumPaymentAmount: 1500, + minimumPaymentAmount: 750, maximumPaymentAmount: 1500000, suggestedPaymentAmounts: [3500, 6500, 15000], zeroDecimalCurrency: true, @@ -449,3 +544,68 @@ paths: schema: type: string default: "Invalid currency" + + /redeem: + get: + summary: Redeem credits gifted via email + description: Redeem credits gifted via email by providing the destination wallet address for the credits, the redemption ID, and recipient email address + + parameters: + - name: destinationAddress + in: query + required: true + schema: + type: string + description: Destination wallet address + + - name: id + in: query + required: true + schema: + type: string + description: ID for the redemption + + - name: email + in: query + required: true + schema: + type: string + description: Recipient email address for the redemption + + responses: + "200": + description: OK + content: + application/json: + schema: + type: object + properties: + message: + type: string + example: Payment receipt redeemed for 1000 winc! + userBalance: + type: string + example: 1000 + userAddress: + type: string + example: abcdefghijklmnopqrxtuvwxyz123456789ABCDEFGH + userCreationDate: + type: string + example: 2023-05-17T21:46:38.404Z + + "400": + description: Bad Request + content: + text/plain: + schema: + type: string + description: "Error message string dependent on cause" + example: "Failure to redeem payment receipt!" + + "503": + description: Service Unavailable + content: + text/plain: + schema: + type: string + default: "Error while redeeming payment receipt. Unable to reach Database!" diff --git a/migrations/20230316184829_initial.ts b/migrations/20230316184829_initial.ts deleted file mode 100644 index 23e7e7d..0000000 --- a/migrations/20230316184829_initial.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { Knex } from "knex"; - -import { Schema } from "../src/database/schema"; - -export async function up(knex: Knex): Promise { - return Schema.create(knex); -} - -export async function down(knex: Knex): Promise { - return Schema.rollback(knex); -} diff --git a/migrations/20230714170814_audit_log.ts b/migrations/20230714170814_audit_log.ts deleted file mode 100644 index 4ffc727..0000000 --- a/migrations/20230714170814_audit_log.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { Knex } from "knex"; - -import { Schema } from "../src/database/schema"; - -export async function up(knex: Knex): Promise { - return Schema.migrateToAuditLog(knex); -} - -export async function down(knex: Knex): Promise { - return Schema.rollbackFromAuditLog(knex); -} diff --git a/migrations/20230717155202_audit_log_credit_pos_neg.ts b/migrations/20230717155202_audit_log_credit_pos_neg.ts deleted file mode 100644 index 2bbe2f3..0000000 --- a/migrations/20230717155202_audit_log_credit_pos_neg.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { Knex } from "knex"; - -import { Schema } from "../src/database/schema"; - -export async function up(knex: Knex): Promise { - return Schema.migrateAuditLogToPositiveNegativeCredits(knex); -} - -export async function down(knex: Knex): Promise { - return Schema.rollBackFromMigrateAuditLogToPositiveNegativeCredits(knex); -} diff --git a/package.json b/package.json index 454601d..d013fa1 100644 --- a/package.json +++ b/package.json @@ -24,11 +24,11 @@ "start:dev": "yarn nodemon lib/index.js", "start:watch": "yarn nodemon -r dotenv/config -r ./src/index.ts", "start:docker": "docker compose up payment-service --build", - "db:up": "docker compose up payment-service-pg -d && yarn db:migrate:latest", - "db:down": "docker compose down ; docker volume rm payment-service_payment-service-data", - "db:migrate:latest": "yarn knex migrate:latest --knexfile \"src/database/knexfile.ts\"", - "db:migrate:rollback": "yarn knex migrate:rollback $* --knexfile \"src/database/knexfile.ts\"", - "db:make:migration": "yarn knex migrate:make $* -x ts --knexfile \"src/database/knexfile.ts\"" + "db:up": "yarn db:down && docker compose up payment-service-pg -d && yarn db:migrate:latest", + "db:down": "docker compose down -v", + "db:migrate:latest": "yarn knex migrate:latest --cwd ./src/database --knexfile knexfile.ts", + "db:migrate:rollback": "yarn knex migrate:rollback $* --cwd ./src/database --knexfile knexfile.ts", + "db:make:migration": "yarn knex migrate:make $* -x ts --cwd ./src/database --knexfile knexfile.ts" }, "devDependencies": { "@istanbuljs/nyc-config-typescript": "^1.0.2", @@ -41,22 +41,22 @@ "@types/koa": "^2.13.4", "@types/koa-router": "^7.4.4", "@types/koa__cors": "^3.3.0", + "@types/mandrill-api": "^1.0.33", "@types/mocha": "^9.1.1", "@types/node": "^18.16.1", "@types/sinon": "^10.0.11", "@types/sinon-chai": "^3.2.9", + "@types/validator": "^13.11.7", "@typescript-eslint/eslint-plugin": "^5.25.0", "@typescript-eslint/parser": "^5.25.0", - "arweave": "^1.13.4", "axios-mock-adapter": "^1.21.2", "chai": "^4.3.6", - "dotenv": "^16.0.3", "eslint": "^8.15.0", "eslint-plugin-header": "^3.1.1", "husky": "^8.0.1", "lint-staged": "^12.5.0", "mocha": "^10.0.0", - "nodemon": "^2.0.16", + "nodemon": "^3.0.1", "nyc": "^15.1.0", "prettier": "^2.6.2", "rimraf": "^3.0.2", @@ -65,25 +65,29 @@ "typescript": "^4.7.4" }, "dependencies": { - "@alexsasharegan/simple-cache": "^3.3.3", + "@ardrive/ardrive-promise-cache": "^1.1.4", "@aws-sdk/client-secrets-manager": "^3.290.0", "@aws-sdk/client-ssm": "^3.369.0", "@koa/cors": "^4.0.0", + "arweave": "^1.13.4", "axios": "0.27.2", "axios-retry": "^3.4.0", "bignumber.js": "^9.1.0", + "dotenv": "^16.0.3", "jsonwebtoken": "^9.0.0", "knex": "^2.5.1", "koa": "2.13.4", "koa-jwt": "^4.0.4", "koa-router": "11.0.1", "koa2-swagger-ui": "^5.8.0", + "mandrill-api": "^1.0.45", "p-limit": "^3.1.0", "pg": "^8.8.0", "prom-client": "^14.1.0", "raw-body": "^2.5.2", "sinon-chai": "^3.7.0", "stripe": "^11.13.0", + "validator": "^13.11.0", "winston": "^3.8.2", "yaml": "^2.2.2" } diff --git a/src/architecture.ts b/src/architecture.ts index edbeb45..388febe 100644 --- a/src/architecture.ts +++ b/src/architecture.ts @@ -17,10 +17,12 @@ import Stripe from "stripe"; import { Database } from "./database/database"; +import { EmailProvider } from "./emailProvider"; import { PricingService } from "./pricing/pricing"; export interface Architecture { paymentDatabase: Database; pricingService: PricingService; stripe: Stripe; + emailProvider?: EmailProvider; } diff --git a/src/cache/promiseCache.test.ts b/src/cache/promiseCache.test.ts deleted file mode 100644 index 936e72e..0000000 --- a/src/cache/promiseCache.test.ts +++ /dev/null @@ -1,150 +0,0 @@ -/** - * Copyright (C) 2022-2023 Permanent Data Solutions, Inc. All Rights Reserved. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -/* eslint-disable @typescript-eslint/no-floating-promises */ -import { expect } from "chai"; - -import { PromiseCache } from "./promiseCache"; - -describe("PromiseCache class", () => { - it("constructor takes a capacity that is not exceeded by excessive puts", async () => { - const cache = new PromiseCache({ cacheCapacity: 1 }); - cache.put("1", Promise.resolve("one")); - cache.put("2", Promise.resolve("two")); - expect(cache.get("1")).to.be.undefined; - expect(cache.get("2")).to.not.be.undefined; - expect(await cache.get("2")).to.equal("two"); - expect(cache.size()).to.equal(1); - }); - - it("preserves most requested entries when over capacity", async () => { - const cache = new PromiseCache({ cacheCapacity: 3 }); - cache.put("1", Promise.resolve("one")); - cache.put("2", Promise.resolve("two")); - cache.put("3", Promise.resolve("three")); - cache.get("1"); - cache.get("3"); - cache.put("4", Promise.resolve("four")); - expect(cache.get("1")).to.not.be.undefined; - expect(cache.get("2")).to.be.undefined; - expect(cache.get("3")).to.not.be.undefined; - expect(cache.get("4")).to.not.be.undefined; - expect(await cache.get("1")).to.equal("one"); - expect(await cache.get("3")).to.equal("three"); - expect(await cache.get("4")).to.equal("four"); - expect(cache.size()).to.equal(3); - }); - - it("caches and retrieves new entries", async () => { - const cache = new PromiseCache({ cacheCapacity: 1 }); - cache.put("1", Promise.resolve("one")); - expect(cache.get("1")).to.not.be.undefined; - expect(await cache.get("1")).to.equal("one"); - expect(cache.size()).to.equal(1); - }); - - it("updates and retrieves existing entries", async () => { - const cache = new PromiseCache({ cacheCapacity: 2 }); - cache.put("1", Promise.resolve("one")); - cache.put("1", Promise.resolve("uno")); - expect(cache.get("1")).to.not.be.undefined; - expect(await cache.get("1")).to.equal("uno"); - expect(cache.size()).to.equal(1); - }); - - it("caches and retrieves different object entries", async () => { - const cache = new PromiseCache, string>({ - cacheCapacity: 2, - }); - const cacheKey1 = { foo: "bar" }; - const cacheKey2 = { bar: "foo" }; - cache.put(cacheKey1, Promise.resolve("foobar")); - cache.put(cacheKey2, Promise.resolve("barfoo")); - expect(cache.get(cacheKey1)).to.not.be.undefined; - expect(await cache.get(cacheKey1)).to.equal("foobar"); - expect(cache.get(cacheKey2)).to.not.be.undefined; - expect(await cache.get(cacheKey2)).to.equal("barfoo"); - expect(cache.size()).to.equal(2); - }); - - describe("remove function", () => { - it("removes a single entry", async () => { - const cache = new PromiseCache({ cacheCapacity: 2 }); - cache.put("1", Promise.resolve("one")); - cache.put("2", Promise.resolve("two")); - expect(cache.get("1")).to.not.be.undefined; - expect(cache.get("2")).to.not.be.undefined; - cache.remove("2"); - expect(cache.get("2")).to.be.undefined; - expect(cache.get("1")).to.not.undefined; - expect(await cache.get("1")).to.equal("one"); - expect(cache.size()).to.equal(1); - }); - }); - - describe("clear function", () => { - it("purges all entries", async () => { - const cache = new PromiseCache({ cacheCapacity: 1 }); - cache.put("1", Promise.resolve("one")); - cache.clear(); - expect(cache.get("1")).to.be.undefined; - expect(cache.size()).to.equal(0); - }); - }); - - describe("size function", () => { - it("returns the correct entry count", async () => { - const cache = new PromiseCache({ cacheCapacity: 2 }); - cache.put("1", Promise.resolve("one")); - cache.put("2", Promise.resolve("two")); - expect(cache.size()).to.equal(2); - }); - }); - - describe("cacheKeyString function", () => { - it("returns and input string as the same string", async () => { - const cache = new PromiseCache({ cacheCapacity: 1 }); - expect(cache.cacheKeyString("key")).to.equal("key"); - expect(cache.cacheKeyString('{ bad: "json"')).to.equal('{ bad: "json"'); - }); - - it("returns an input number as a string", async () => { - const cache = new PromiseCache({ cacheCapacity: 1 }); - expect(cache.cacheKeyString(1)).to.equal("1"); - }); - - it("returns an input object as its JSON representation", async () => { - const cache = new PromiseCache, string>({ - cacheCapacity: 1, - }); - expect(cache.cacheKeyString({ foo: "bar" })).to.equal('{"foo":"bar"}'); - }); - }); - - describe("time to live", () => { - it("purges all entries after ttl", async () => { - const cache = new PromiseCache({ - cacheCapacity: 1, - cacheTTL: 10, - }); - cache.put("1", Promise.resolve("one")); - await new Promise((resolve) => setTimeout(resolve, 15)); - expect(cache.get("1")).to.be.undefined; - expect(cache.size()).to.equal(0); - }); - }); -}); diff --git a/src/cache/promiseCache.ts b/src/cache/promiseCache.ts deleted file mode 100644 index 8bab61a..0000000 --- a/src/cache/promiseCache.ts +++ /dev/null @@ -1,58 +0,0 @@ -/** - * Copyright (C) 2022-2023 Permanent Data Solutions, Inc. All Rights Reserved. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ -import { Cache, EphemeralCache } from "@alexsasharegan/simple-cache"; - -import { msPerMinute } from "../constants"; - -export interface CacheParams { - cacheCapacity: number; - cacheTTL?: number; -} -export class PromiseCache { - private cache: Cache>; - - constructor({ cacheCapacity, cacheTTL = msPerMinute * 1 }: CacheParams) { - this.cache = EphemeralCache>(cacheCapacity, cacheTTL); - } - - cacheKeyString(key: K): string { - // Note: This implementation may not sufficiently differentiate keys - // for certain object types depending on their toJSON implementation - return typeof key === "string" ? key : JSON.stringify(key); - } - - put(key: K, value: Promise): Promise { - this.cache.write(this.cacheKeyString(key), value); - return value; - } - - get(key: K): Promise | undefined { - return this.cache.read(this.cacheKeyString(key)); - } - - remove(key: K): void { - this.cache.remove(this.cacheKeyString(key)); - } - - clear(): void { - this.cache.clear(); - } - - size(): number { - return this.cache.size(); - } -} diff --git a/src/cache/readThroughPromiseCache.test.ts b/src/cache/readThroughPromiseCache.test.ts deleted file mode 100644 index b6929af..0000000 --- a/src/cache/readThroughPromiseCache.test.ts +++ /dev/null @@ -1,119 +0,0 @@ -/** - * Copyright (C) 2022-2023 Permanent Data Solutions, Inc. All Rights Reserved. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ -import { expect } from "chai"; -import { describe, it } from "mocha"; - -import { ReadThroughPromiseCache } from "./readThroughPromiseCache"; - -describe("ReadThroughPromiseCache Class", () => { - it("should be able to cache and retrieve new entries", async () => { - // Test function to make sure we return a different value after the first call - // Since function is passed in the constructor - - let testTracker = 0; - - const testFunction = async () => { - if (testTracker < 1) { - testTracker++; - return "one"; - } else { - return "two"; - } - }; - const cache = new ReadThroughPromiseCache({ - cacheParams: { cacheCapacity: 10 }, - readThroughFunction: testFunction, - }); - - expect(await cache.get("1")).to.equal("one"); - expect(await cache.get("1")).to.equal("one"); - }); - - it("should throw error if readthrough function throws", async () => { - let testTracker = 0; - - const testFunction = async () => { - if (testTracker < 1) { - testTracker++; - throw new Error("test error"); - } else { - return "two"; - } - }; - const cache = new ReadThroughPromiseCache({ - cacheParams: { cacheCapacity: 10 }, - readThroughFunction: testFunction, - }); - try { - await cache.get("1"); - expect.fail("Error: test error"); - } catch (error) { - expect(error).to.exist; - } - expect(await cache.get("1")).to.equal("two"); - }); - - it("should purge all entries after ttl expires", async () => { - // Test function to make sure we return a different value after the first call - // Since function is passed in the constructor - - let testTracker = 0; - - const testFunction = async () => { - if (testTracker < 1) { - testTracker++; - return "one"; - } else { - return "two"; - } - }; - - const cache = new ReadThroughPromiseCache({ - cacheParams: { cacheCapacity: 10, cacheTTL: 5 }, - readThroughFunction: testFunction, - }); - - expect(await cache.get("1")).to.equal("one"); - await new Promise((resolve) => setTimeout(resolve, 10)); - expect(await cache.get("1")).to.equal("two"); - }); - - it("preserves most requested entries when over capacity", async () => { - // Test function to make sure we return a different value after the first call - // Since function is passed in the constructor - let testTracker = 0; - - const testFunction = async () => { - if (testTracker < 1) { - testTracker++; - return "one"; - } else { - return "two"; - } - }; - - const cache = new ReadThroughPromiseCache({ - cacheParams: { cacheCapacity: 1, cacheTTL: 5 }, - readThroughFunction: testFunction, - }); - - expect(await cache.get("1")).to.equal("one"); - expect(await cache.get("1")).to.equal("one"); - expect(await cache.get("2")).to.equal("two"); - expect(await cache.get("1")).to.equal("two"); - }); -}); diff --git a/src/cache/readThroughPromiseCache.ts b/src/cache/readThroughPromiseCache.ts deleted file mode 100644 index 9482c39..0000000 --- a/src/cache/readThroughPromiseCache.ts +++ /dev/null @@ -1,62 +0,0 @@ -/** - * Copyright (C) 2022-2023 Permanent Data Solutions, Inc. All Rights Reserved. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ -import logger from "../logger"; -import { CacheParams, PromiseCache } from "./promiseCache"; - -interface ReadThroughPromiseCacheParams { - /** - * @example - readThroughFunction = () => { - // try elastic cache - if hit, return it - else try fiatOracle - return myArweaveToFiatOracle.getFiatPerOneAR(); - } - */ - readThroughFunction: (key: K) => Promise; - cacheParams: CacheParams; -} - -export class ReadThroughPromiseCache { - private readonly cache: PromiseCache; - private readonly readThroughFunction: (key: K) => Promise; - constructor({ - cacheParams, - readThroughFunction, - }: ReadThroughPromiseCacheParams) { - this.cache = new PromiseCache(cacheParams); - this.readThroughFunction = readThroughFunction; - } - - get(key: K): Promise { - const cachedValue = this.cache.get(key); - if (cachedValue) { - return cachedValue; - } - - const valuePromise = this.readThroughFunction(key); - - valuePromise.catch(() => { - logger.error(`Error getting value for key ${key}`); - this.cache.remove(key); - }); - - void this.cache.put(key, valuePromise); - - return valuePromise; - } -} diff --git a/src/constants.ts b/src/constants.ts index a1bd123..ab151de 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -18,6 +18,7 @@ import { ByteCount } from "./types/byteCount"; import { SupportedPaymentCurrencyTypes } from "./types/supportedCurrencies"; export const isTestEnv = process.env.NODE_ENV === "test"; +export const migrateOnStartup = process.env.MIGRATE_ON_STARTUP === "true"; export const defaultPort = +(process.env.PORT ?? 3000); export const msPerMinute = 1000 * 60; export const oneHourInSeconds = 3600; @@ -39,60 +40,58 @@ export const topUpMethods = [ export const TEST_PRIVATE_ROUTE_SECRET = "test-secret"; -export const turboFeePercentageAsADecimal = 0.23; - // cspell:disable export const electronicallySuppliedServicesTaxCode = "txcd_10000000"; //cspell:disable /** Min, maximumPaymentAmount, and suggestedPaymentAmountsested payment amounts for the payment service */ export const paymentAmountLimits: CurrencyLimitations = { aud: { - minimumPaymentAmount: 15_00, + minimumPaymentAmount: 7500, maximumPaymentAmount: 15_000_00, suggestedPaymentAmounts: [25_00, 75_00, 150_00], }, brl: { - minimumPaymentAmount: 50_00, + minimumPaymentAmount: 2500, maximumPaymentAmount: 50_000_00, suggestedPaymentAmounts: [125_00, 250_00, 500_00], }, cad: { - minimumPaymentAmount: 10_00, + minimumPaymentAmount: 500, maximumPaymentAmount: 15_000_00, suggestedPaymentAmounts: [25_00, 50_00, 100_00], }, eur: { - minimumPaymentAmount: 10_00, + minimumPaymentAmount: 500, maximumPaymentAmount: 10_000_00, suggestedPaymentAmounts: [25_00, 50_00, 100_00], }, gbp: { - minimumPaymentAmount: 10_00, + minimumPaymentAmount: 500, maximumPaymentAmount: 10_000_00, suggestedPaymentAmounts: [20_00, 40_00, 80_00], }, hkd: { - minimumPaymentAmount: 100_00, + minimumPaymentAmount: 5000, maximumPaymentAmount: 100_000_00, suggestedPaymentAmounts: [200_00, 400_00, 800_00], }, inr: { - minimumPaymentAmount: 1000_00, + minimumPaymentAmount: 50_000, maximumPaymentAmount: 900_000_00, suggestedPaymentAmounts: [2000_00, 4000_00, 8000_00], }, jpy: { - minimumPaymentAmount: 1_500, + minimumPaymentAmount: 750, maximumPaymentAmount: 1_500_000, suggestedPaymentAmounts: [3_500, 6_500, 15_000], }, sgd: { - minimumPaymentAmount: 15_00, + minimumPaymentAmount: 750, maximumPaymentAmount: 15_000_00, suggestedPaymentAmounts: [25_00, 75_00, 150_00], }, usd: { - minimumPaymentAmount: 10_00, + minimumPaymentAmount: 500, maximumPaymentAmount: 10_000_00, suggestedPaymentAmounts: [25_00, 50_00, 100_00], }, @@ -311,3 +310,15 @@ export const recognizedCountries = [ "Zambia", "Zimbabwe", ] as const; + +export const promoCodeBackfills = { + welcomeTwentyPercentOff: "TOKEN2049", +}; + +export const maxGiftMessageLength = process.env.MAX_GIFT_MESSAGE_LENGTH ?? 250; + +export const giftingEmailAddress = + process.env.GIFTING_EMAILL_ADDRESS ?? "gift@ardrive.io"; + +/** gifting on top up via email depends on GIFTING_ENABLED="true" env var */ +export const isGiftingEnabled = process.env.GIFTING_ENABLED === "true"; diff --git a/src/database/database.ts b/src/database/database.ts index ee66e6e..b47f583 100644 --- a/src/database/database.ts +++ b/src/database/database.ts @@ -19,14 +19,19 @@ import { WC } from "../types/arc"; import { ChargebackReceipt, ChargebackReceiptId, + CreateBalanceReservationParams, CreateChargebackReceiptParams, CreatePaymentReceiptParams, CreateTopUpQuoteParams, + PaymentAdjustmentCatalog, PaymentReceipt, PaymentReceiptId, PromotionalInfo, + SingleUseCodePaymentCatalog, TopUpQuote, TopUpQuoteId, + UnredeemedGift, + UploadAdjustmentCatalog, User, UserAddress, } from "./dbTypes"; @@ -43,19 +48,17 @@ export interface Database { getBalance: (userAddress: UserAddress) => Promise; createPaymentReceipt: ( paymentReceipt: CreatePaymentReceiptParams - ) => Promise; + ) => Promise; getPaymentReceipt: ( paymentReceiptId: PaymentReceiptId ) => Promise; reserveBalance: ( - userAddress: UserAddress, - winstonCreditAmount: WC, - dataItemId?: TransactionId + createBalanceReservationParams: CreateBalanceReservationParams ) => Promise; refundBalance: ( userAddress: UserAddress, winstonCreditAmount: WC, - dataItemId?: TransactionId + dataItemId?: TransactionId // TODO: once the upload-service is updated with the new routes, make this required ) => Promise; createChargebackReceipt: ( createChargebackReceiptParams: CreateChargebackReceiptParams @@ -69,4 +72,15 @@ export interface Database { checkForExistingPaymentByTopUpQuoteId: ( topUpQuoteId: TopUpQuoteId ) => Promise; + getSingleUsePromoCodeAdjustments: ( + promoCodes: string[], + userAddress: UserAddress + ) => Promise; + getUploadAdjustmentCatalogs: () => Promise; + getPaymentAdjustmentCatalogs(): Promise; + redeemGift: (params: { + paymentReceiptId: string; + recipientEmail: string; + destinationAddress: string; + }) => Promise; } diff --git a/src/database/dbConstants.ts b/src/database/dbConstants.ts index 8a19399..e88d8de 100644 --- a/src/database/dbConstants.ts +++ b/src/database/dbConstants.ts @@ -21,6 +21,19 @@ export const tableNames = { paymentReceipt: "payment_receipt", chargebackReceipt: "chargeback_receipt", auditLog: "audit_log", + balanceReservation: "balance_reservation", + // TODO: refundedReservation, finalizedReservation + + uploadAdjustment: "upload_adjustment", + paymentAdjustment: "payment_adjustment", + + uploadAdjustmentCatalog: "upload_adjustment_catalog", + paymentAdjustmentCatalog: "payment_adjustment_catalog", + singleUseCodePaymentAdjustmentCatalog: + "single_use_code_payment_adjustment_catalog", + + unredeemedGift: "unredeemed_gift", + redeemedGift: "redeemed_gift", } as const; export const columnNames = { @@ -35,12 +48,14 @@ export const columnNames = { topUpQuoteId: "top_up_quote_id", destinationAddress: "destination_address", destinationAddressType: "destination_address_type", - paymentAmount: "payment_amount", + paymentAmount: "payment_amount", // amount we will expect from payment provider + quotedPaymentAmount: "quoted_payment_amount", // amount before payment-exclusive adjustments currencyType: "currency_type", winstonCreditAmount: "winston_credit_amount", quoteExpirationDate: "quote_expiration_date", quoteCreationDate: "quote_creation_date", paymentProvider: "payment_provider", + giftMessage: "gift_message", // Optional gift message, ignored in non-gift top-ups for now // Failed top up quote failedReason: "failed_reason", @@ -55,9 +70,53 @@ export const columnNames = { chargebackReason: "chargeback_reason", chargebackReceiptDate: "chargeback_receipt_date", + // Balance reservation + reservationId: "reservation_id", + dataItemId: "data_item_id", + reservedDate: "reserved_date", + networkWincAmount: "network_winc_amount", // amount before adjustments + reservedWincAmount: "reserved_winc_amount", // amount reserved after adjustments + + // Adjustments + adjustmentId: "adjustment_id", + adjustmentDate: "adjustment_date", + adjustmentIndex: "adjustment_index", // ordering index with which adjustment should be applied to associated reservation_id + adjustedWincAmount: "adjusted_winc_amount", + adjustedPaymentAmount: "adjusted_payment_amount", + adjustedCurrencyType: "adjusted_currency_type", + // Audit Log auditId: "audit_id", auditDate: "audit_date", changeReason: "change_reason", changeId: "change_id", + + // Adjustment Catalog + catalogId: "catalog_id", + adjustmentName: "adjustment_name", + adjustmentDescription: "adjustment_description", + adjustmentStartDate: "adjustment_start_date", + adjustmentEndDate: "adjustment_end_date", + + adjustmentPriority: "adjustment_priority", + // inclusive: applied within the payment that is made, exclusive: applied before payment is made where user can see the adjustment + adjustmentExclusivity: "adjustment_exclusivity", + adjustmentCodeValue: "code_value", + targetUserGroup: "target_user_group", + maxUses: "max_uses", + minimumPaymentAmount: "minimum_payment_amount", + maximumDiscountAmount: "maximum_discount_amount", + + operator: "operator", + operatorMagnitude: "operator_magnitude", + + // Unredeemed Gift + giftedWincAmount: "gifted_winc_amount", + recipientEmail: "recipient_email", + senderEmail: "sender_email", + creationDate: "creation_date", + expirationDate: "expiration_date", + + // Redeemed Gift + redemptionDate: "redemption_date", } as const; diff --git a/src/database/dbMaps.ts b/src/database/dbMaps.ts index 72d631f..6c85c81 100644 --- a/src/database/dbMaps.ts +++ b/src/database/dbMaps.ts @@ -16,16 +16,28 @@ */ import { Winston } from "../types/winston"; import { + AdjustmentCatalog, + AdjustmentCatalogDBResult, ChargebackReceipt, ChargebackReceiptDBResult, + DestinationAddressType, FailedTopUpQuote, FailedTopUpQuoteDBResult, + PaymentAdjustmentCatalog, + PaymentAdjustmentCatalogDBResult, PaymentReceipt, PaymentReceiptDBResult, PromotionalInfo, + SingleUseCodePaymentCatalog, + SingleUseCodePaymentCatalogDBResult, TopUpQuote, TopUpQuoteDBResult, + UnredeemedGift, + UnredeemedGiftDBResult, + UploadAdjustmentCatalog, + UploadAdjustmentCatalogDBResult, User, + UserAddressType, UserDBResult, } from "./dbTypes"; @@ -39,7 +51,7 @@ export function userDBMap({ return { promotionalInfo: promotional_info as PromotionalInfo, userAddress: user_address, - userAddressType: user_address_type, + userAddressType: user_address_type as UserAddressType, userCreationDate: user_creation_date, winstonCreditBalance: new Winston(winston_credit_balance), }; @@ -47,6 +59,7 @@ export function userDBMap({ export function topUpQuoteDBMap({ payment_amount, + quoted_payment_amount, currency_type, payment_provider, top_up_quote_id, @@ -58,13 +71,14 @@ export function topUpQuoteDBMap({ }: TopUpQuoteDBResult): TopUpQuote { return { paymentAmount: +payment_amount, + quotedPaymentAmount: +quoted_payment_amount, currencyType: currency_type, paymentProvider: payment_provider, topUpQuoteId: top_up_quote_id, quoteCreationDate: quote_creation_date, quoteExpirationDate: quote_expiration_date, destinationAddress: destination_address, - destinationAddressType: destination_address_type, + destinationAddressType: destination_address_type as DestinationAddressType, winstonCreditAmount: new Winston(winston_credit_amount), }; } @@ -99,3 +113,67 @@ export function chargebackReceiptDBMap( chargebackReceiptId: dbResult.chargeback_receipt_id, }; } + +function priceAdjustmentCatalogDBMap({ + catalog_id, + adjustment_name, + adjustment_description, + operator, + operator_magnitude, + adjustment_priority, + adjustment_start_date, + adjustment_end_date, +}: AdjustmentCatalogDBResult): AdjustmentCatalog { + return { + catalogId: catalog_id, + name: adjustment_name, + description: adjustment_description, + startDate: adjustment_start_date, + endDate: adjustment_end_date, + priority: adjustment_priority, + operator, + operatorMagnitude: +operator_magnitude, + }; +} + +export function uploadAdjustmentCatalogDBMap( + dbResult: UploadAdjustmentCatalogDBResult +): UploadAdjustmentCatalog { + return priceAdjustmentCatalogDBMap(dbResult); +} + +export function paymentAdjustmentCatalogDBMap( + dbResult: PaymentAdjustmentCatalogDBResult +): PaymentAdjustmentCatalog { + return { + ...priceAdjustmentCatalogDBMap(dbResult), + exclusivity: dbResult.adjustment_exclusivity, + }; +} + +export function singleUseCodePaymentCatalogDBMap( + dbResult: SingleUseCodePaymentCatalogDBResult +): SingleUseCodePaymentCatalog { + return { + ...paymentAdjustmentCatalogDBMap(dbResult), + codeValue: dbResult.code_value, + targetUserGroup: dbResult.target_user_group, + maxUses: dbResult.max_uses, + minimumPaymentAmount: dbResult.minimum_payment_amount, + maximumDiscountAmount: dbResult.maximum_discount_amount, + }; +} + +export function unredeemedGiftDBMap( + dbResult: UnredeemedGiftDBResult +): UnredeemedGift { + return { + paymentReceiptId: dbResult.payment_receipt_id, + giftedWincAmount: new Winston(dbResult.gifted_winc_amount), + recipientEmail: dbResult.recipient_email, + giftMessage: dbResult.gift_message, + giftCreationDate: dbResult.creation_date, + giftExpirationDate: dbResult.expiration_date, + senderEmail: dbResult.sender_email, + }; +} diff --git a/src/database/dbTypes.ts b/src/database/dbTypes.ts index 5ad87e5..3be4250 100644 --- a/src/database/dbTypes.ts +++ b/src/database/dbTypes.ts @@ -14,21 +14,40 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ +import { FinalPrice, NetworkPrice } from "../pricing/price"; import { PublicArweaveAddress } from "../types"; import { WC } from "../types/arc"; -export type Adjustment = { +export interface Adjustment { name: string; description: string; /** value to calculate adjustment ( Multiplier or Added Value ) */ - value: number; + operatorMagnitude: number; operator: "multiply" | "add"; + adjustmentAmount: WC | number; + catalogId: IdType; +} + +export interface UploadAdjustment extends Adjustment { /** Amount of winc this adjustment changes (e.g -600 for 600 winc saved) */ adjustmentAmount: WC; -}; +} + +export interface PaymentAdjustment extends Adjustment { + /** Amount of payment amount (usd, eur, btc) this adjustment changes (e.g -600 for 600 dollars saved) */ + adjustmentAmount: PaymentAmount; + currencyType: CurrencyType; + maxDiscount?: number; + promoCode?: string; +} export type UserAddress = string | PublicArweaveAddress; -export type UserAddressType = string | "arweave"; + +export const userAddressTypes = ["arweave"] as const; +export type UserAddressType = (typeof userAddressTypes)[number]; + +export const destinationAddressTypes = ["email", "arweave"] as const; +export type DestinationAddressType = (typeof destinationAddressTypes)[number]; /** Currently using Postgres Date type (ISO String) */ export type Timestamp = string; @@ -41,6 +60,7 @@ export type JsonSerializable = | { [member: string]: JsonSerializable } | JsonSerializable[]; +// TODO: Promotional Info Schema. We will use JSON object export type PromotionalInfo = Record; type IdType = string; @@ -48,12 +68,16 @@ type IdType = string; export type TopUpQuoteId = IdType; export type PaymentReceiptId = IdType; export type ChargebackReceiptId = IdType; +export type ReservationId = IdType; +export type AdjustmentId = IdType; + +export type DataItemId = IdType; export type PaymentAmount = number; export type CurrencyType = string; -export type PaymentProvider = string | "stripe"; +export type PaymentProvider = string | "stripe"; // TODO: "apple-pay" export interface User { userAddress: UserAddress; @@ -66,16 +90,20 @@ export interface User { export interface TopUpQuote { topUpQuoteId: TopUpQuoteId; destinationAddress: UserAddress; - destinationAddressType: UserAddressType; + destinationAddressType: DestinationAddressType; paymentAmount: PaymentAmount; + quotedPaymentAmount: PaymentAmount; currencyType: CurrencyType; winstonCreditAmount: WC; quoteExpirationDate: Timestamp; quoteCreationDate: Timestamp; paymentProvider: PaymentProvider; + giftMessage?: string; } -export type CreateTopUpQuoteParams = Omit; +export type CreateTopUpQuoteParams = Omit & { + adjustments: PaymentAdjustment[]; +}; export interface FailedTopUpQuote extends TopUpQuote { failedReason: "expired" | string; @@ -91,6 +119,7 @@ export interface CreatePaymentReceiptParams { topUpQuoteId: TopUpQuoteId; paymentAmount: PaymentAmount; currencyType: CurrencyType; + senderEmail?: string; } export interface ChargebackReceipt extends PaymentReceipt { @@ -105,6 +134,48 @@ export type CreateChargebackReceiptParams = { chargebackReceiptId: ChargebackReceiptId; }; +export interface BalanceReservation { + reservationId: ReservationId; + dataItemId: DataItemId; + userAddress: UserAddress; + reservedDate: Timestamp; + reservedWincAmount: WC; + networkWincAmount: WC; +} + +export type CreateBalanceReservationParams = { + dataItemId: DataItemId; + userAddress: UserAddress; + reservedWincAmount: FinalPrice; + networkWincAmount: NetworkPrice; + adjustments: UploadAdjustment[]; +}; + +export interface AdjustmentCatalog { + catalogId: IdType; + name: string; + description: string; + startDate: Timestamp; + endDate?: Timestamp; + operator: "add" | "multiply"; + operatorMagnitude: number; + priority: number; +} + +export type UploadAdjustmentCatalog = AdjustmentCatalog; + +export interface PaymentAdjustmentCatalog extends AdjustmentCatalog { + exclusivity: Exclusivity; +} + +export interface SingleUseCodePaymentCatalog extends PaymentAdjustmentCatalog { + codeValue: string; + targetUserGroup: TargetUserGroup; + maxUses: number; + minimumPaymentAmount: number; + maximumDiscountAmount: number; +} + export interface UserDBInsert { user_address: string; user_address_type: string; @@ -116,7 +187,10 @@ export type AuditChangeReason = | "payment" | "account_creation" | "chargeback" - | "refund"; + | "refund" + | "gifted_payment" + | "gifted_payment_redemption" + | "gifted_account_creation"; export interface AuditLogInsert { user_address: string; @@ -127,6 +201,7 @@ export interface AuditLogInsert { export interface AuditLogDBResult extends AuditLogInsert { audit_id: number; + audit_date: string; } export interface UserDBResult extends UserDBInsert { @@ -139,10 +214,12 @@ export interface TopUpQuoteDBInsert { destination_address: string; destination_address_type: string; payment_amount: string; + quoted_payment_amount: string; currency_type: string; winston_credit_amount: string; payment_provider: string; quote_expiration_date: string; + gift_message?: string; } export interface TopUpQuoteDBResult extends TopUpQuoteDBInsert { @@ -173,3 +250,138 @@ export interface ChargebackReceiptDBInsert extends PaymentReceiptDBResult { export interface ChargebackReceiptDBResult extends ChargebackReceiptDBInsert { chargeback_receipt_date: string; } + +export interface BalanceReservationDBInsert { + reservation_id: string; + data_item_id: string; + user_address: string; + reserved_winc_amount: string; + network_winc_amount: string; +} + +export interface BalanceReservationDBResult extends BalanceReservationDBInsert { + reserved_date: string; +} + +interface AdjustmentCatalogDBInsert { + catalog_id: string; + adjustment_name: string; + adjustment_description?: string; + + adjustment_start_date?: string; + adjustment_end_date?: string; + + operator: "add" | "multiply"; + operator_magnitude: string; + adjustment_priority?: number; +} + +export type UploadAdjustmentCatalogDBInsert = AdjustmentCatalogDBInsert; + +export const exclusivity = ["inclusive", "exclusive"] as const; +export type Exclusivity = (typeof exclusivity)[number]; + +export interface PaymentAdjustmentCatalogDBInsert + extends AdjustmentCatalogDBInsert { + adjustment_exclusivity?: Exclusivity; +} + +type TargetUserGroup = "all" | "new" | "existing"; + +export interface SingleUseCodePaymentCatalogDBInsert + extends PaymentAdjustmentCatalogDBInsert { + code_value: string; + target_user_group?: TargetUserGroup; + max_uses?: number; + minimum_payment_amount?: number; + maximum_discount_amount?: number; +} + +export interface AdjustmentCatalogDBResult extends AdjustmentCatalogDBInsert { + adjustment_start_date: string; + adjustment_priority: number; + adjustment_description: string; +} + +export type UploadAdjustmentCatalogDBResult = AdjustmentCatalogDBResult & + UploadAdjustmentCatalogDBInsert; + +export type PaymentAdjustmentCatalogDBResult = AdjustmentCatalogDBResult & + PaymentAdjustmentCatalogDBInsert & { + adjustment_exclusivity: Exclusivity; + }; + +export interface SingleUseCodePaymentCatalogDBResult + extends PaymentAdjustmentCatalogDBResult { + code_value: string; + target_user_group: TargetUserGroup; + max_uses: number; + minimum_payment_amount: number; + maximum_discount_amount: number; +} + +interface AdjustmentDBInsert { + catalog_id: string; + adjustment_index: number; + user_address: string; +} + +export interface UploadAdjustmentDBInsert extends AdjustmentDBInsert { + reservation_id: string; + adjusted_winc_amount: string; +} + +export interface AdjustmentDBResult extends AdjustmentDBInsert { + adjustment_id: number; + adjustment_date: string; +} + +export interface UploadAdjustmentDBResult + extends UploadAdjustmentDBInsert, + AdjustmentDBResult {} + +export interface PaymentAdjustmentDBInsert extends AdjustmentDBInsert { + top_up_quote_id: string; + adjusted_payment_amount: string; + adjusted_currency_type: string; +} + +export interface PaymentAdjustmentDBResult + extends PaymentAdjustmentDBInsert, + AdjustmentDBResult {} + +export interface UnredeemedGiftDBInsert { + payment_receipt_id: string; + gifted_winc_amount: string; + recipient_email: string; + sender_email?: string; + gift_message?: string; +} + +export interface UnredeemedGiftDBResult extends UnredeemedGiftDBInsert { + creation_date: string; + expiration_date: string; +} + +export interface RedeemedGiftDBInsert extends UnredeemedGiftDBResult { + destination_address: string; +} + +export interface RedeemedGiftDBResult extends RedeemedGiftDBInsert { + redemption_date: string; +} + +export interface UnredeemedGift { + paymentReceiptId: PaymentReceiptId; + giftedWincAmount: WC; + recipientEmail: string; + senderEmail?: string; + giftMessage?: string; + giftCreationDate: Timestamp; + giftExpirationDate: Timestamp; +} + +export interface RedeemedGift extends UnredeemedGift { + destinationAddress: UserAddress; + redemptionDate: Timestamp; +} diff --git a/src/database/errors.ts b/src/database/errors.ts index abf2aa9..b8ce2d3 100644 --- a/src/database/errors.ts +++ b/src/database/errors.ts @@ -14,7 +14,7 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -import { CurrencyType, PaymentAmount, UserAddress } from "./dbTypes"; +import { CurrencyType, PaymentAmount, Timestamp, UserAddress } from "./dbTypes"; export class UserNotFoundWarning extends Error { constructor(userAddress: UserAddress) { @@ -75,3 +75,60 @@ export class PaymentAmountTooLarge extends PaymentValidationError { this.name = "PaymentAmountTooLarge"; } } + +export abstract class PromoCodeError extends Error {} + +export class UserIneligibleForPromoCode extends PromoCodeError { + constructor(userAddress: UserAddress, promoCode: string) { + super( + `The user '${userAddress}' is ineligible for the promo code '${promoCode}'` + ); + this.name = "UserIneligibleForPromoCode"; + } +} + +export class PromoCodeNotFound extends PromoCodeError { + constructor(promoCode: string) { + super(`No promo code found with code '${promoCode}'`); + this.name = "PromoCodeNotFound"; + } +} + +export class PromoCodeExpired extends PromoCodeError { + constructor(promoCode: string, endDate: Timestamp) { + super(`The promo code '${promoCode}' expired on '${endDate}'`); + this.name = "PromoCodeExpired"; + } +} + +export class PaymentAmountTooSmallForPromoCode extends PromoCodeError { + constructor(promoCode: string, minimumPaymentAmount: PaymentAmount) { + super( + `The promo code '${promoCode}' can only used on payments above '${minimumPaymentAmount}'` + ); + this.name = "PaymentAmountTooSmallForPromoCode"; + } +} + +export class PromoCodeExceedsMaxUses extends PromoCodeError { + constructor(promoCode: string, maxUses: number) { + super( + `The promo code '${promoCode}' has already been used the maximum number of times (${maxUses})` + ); + this.name = "PromoCodeExceedsMaxUses"; + } +} + +export class GiftRedemptionError extends Error { + constructor(errorMessage = "Failure to redeem payment receipt!") { + super(errorMessage); + this.name = "GiftRedemptionError"; + } +} + +export class GiftAlreadyRedeemed extends GiftRedemptionError { + constructor() { + super("Gift has already been redeemed!"); + this.name = "GiftAlreadyRedeemed"; + } +} diff --git a/src/database/knexConfig.ts b/src/database/knexConfig.ts index ec91a8a..c6f9e2b 100644 --- a/src/database/knexConfig.ts +++ b/src/database/knexConfig.ts @@ -17,13 +17,14 @@ // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore import KnexDialect from "knex/lib/dialects/postgres"; +import path from "path"; const baseConfig = { client: KnexDialect, version: "13.8", migrations: { tableName: "knex_migrations", - directory: "../../migrations", + directory: path.join(__dirname, "../migrations"), }, }; diff --git a/src/database/migration.ts b/src/database/migration.ts new file mode 100644 index 0000000..f048d2d --- /dev/null +++ b/src/database/migration.ts @@ -0,0 +1,487 @@ +/** + * Copyright (C) 2022-2023 Permanent Data Solutions, Inc. All Rights Reserved. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +import { randomUUID } from "crypto"; +import { Knex } from "knex"; + +import { promoCodeBackfills } from "../constants"; +import globalLogger from "../logger"; +import { Winston } from "../types"; +import { tableNames } from "./dbConstants"; +import { + AuditLogDBResult, + BalanceReservationDBInsert, + BalanceReservationDBResult, + PaymentAdjustmentCatalogDBInsert, + PaymentAdjustmentCatalogDBResult, + PaymentAdjustmentDBInsert, + PaymentAdjustmentDBResult, + PaymentReceiptDBResult, + SingleUseCodePaymentCatalogDBInsert, + SingleUseCodePaymentCatalogDBResult, + UploadAdjustmentCatalogDBInsert, + UploadAdjustmentCatalogDBResult, + UploadAdjustmentDBInsert, + UploadAdjustmentDBResult, +} from "./dbTypes"; + +export async function backfillBalanceReservations(knex: Knex) { + globalLogger.info("Starting balance reservation backfill..."); + const backfillStartTime = Date.now(); + const isoTimestampOfSubsidy = "2023-07-18T20:20:35.000Z"; + const uploadAuditLogsSinceSubsidy = await knex( + tableNames.auditLog + ) + .where("change_reason", "upload") + .andWhere("audit_date", ">", isoTimestampOfSubsidy); + + globalLogger.info("Backfilling balance reservations from audit log:", { + lengthOfAuditLogs: uploadAuditLogsSinceSubsidy.length, + }); + + const subsidyPercentageAsDecimal = 0.6; + + // @ts-expect-error - At time of backfill, these types were compatible. They were changed in Promo Code Migration + const balResInsertsAndAdjustments: [ + BalanceReservationDBInsert, + Omit + ][] = uploadAuditLogsSinceSubsidy.map( + ({ winston_credit_amount, user_address, change_id, audit_date }) => { + // remove negative from audit log winc value + const adjustedWinc = new Winston(winston_credit_amount.replace(/^-/, "")); + + // Reverse the subsidy calculation + const adjustmentAmount = adjustedWinc.times( + subsidyPercentageAsDecimal / (1 - subsidyPercentageAsDecimal) + ); + + // Reverse the `adjustmentAmount` calculation to get the original `winc` + const originalAmount = adjustedWinc.plus(adjustmentAmount); + return [ + { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + data_item_id: change_id!, + reservation_id: randomUUID(), + user_address, + reserved_winc_amount: adjustedWinc.toString(), + network_winc_amount: originalAmount.toString(), + reserved_date: audit_date, + }, + { + adjustment_name: "FWD Research July 2023 Subsidy", + adjustment_description: "A 60% discount for uploads over 500KiB", + operator: "multiply", + operator_magnitude: 0.4, + adjusted_winc_amount: `-${adjustmentAmount.toString()}`, + adjustment_index: 0, + adjustment_date: audit_date, + }, + ]; + } + ); + + const reservationBatchInsertResult = await knex + .batchInsert( + tableNames.balanceReservation, + balResInsertsAndAdjustments.map(([balRes]) => balRes), + 100 + ) + .returning("reservation_id"); + + const adjustmentInserts = balResInsertsAndAdjustments.map(([, adj], i) => { + return { + ...adj, + reservation_id: reservationBatchInsertResult[i].reservation_id, + }; + }); + + await knex.batchInsert(tableNames.uploadAdjustment, adjustmentInserts, 100); + + globalLogger.info("Finished balance reservation backfill!", { + backfillMs: Date.now() - backfillStartTime, + }); +} + +export async function addFwdResearchSubsidyUploadCatalogs(knex: Knex) { + const julAugDbInsert: UploadAdjustmentCatalogDBInsert = { + catalog_id: randomUUID(), + adjustment_start_date: new Date("2023-07-15T00:00:00.000Z").toISOString(), + adjustment_end_date: new Date("2023-08-15T00:00:00.000Z").toISOString(), + adjustment_name: "FWD Research July - August '23 Upload Subsidy", + adjustment_description: "A 60% discount for uploads", + operator: "multiply", + operator_magnitude: "0.4", + }; + await knex( + tableNames.uploadAdjustmentCatalog + ).insert(julAugDbInsert); + + const augSepDbInsert: UploadAdjustmentCatalogDBInsert = { + catalog_id: randomUUID(), + adjustment_start_date: new Date("2023-08-15T00:00:00.000Z").toISOString(), + adjustment_end_date: new Date("2023-09-15T00:00:00.000Z").toISOString(), + adjustment_name: "FWD Research August - September '23 Upload Subsidy", + adjustment_description: "A 52.25% discount for uploads", + operator: "multiply", + operator_magnitude: "0.475", + }; + await knex( + tableNames.uploadAdjustmentCatalog + ).insert(augSepDbInsert); + + const sepOctDbInsert: UploadAdjustmentCatalogDBInsert = { + catalog_id: randomUUID(), + adjustment_start_date: new Date("2023-09-15T00:00:00.000Z").toISOString(), + adjustment_end_date: new Date("2023-10-15T00:00:00.000Z").toISOString(), + adjustment_name: "FWD Research September - October '23 Upload Subsidy", + adjustment_description: "A 45% discount for uploads", + operator: "multiply", + operator_magnitude: "0.55", + }; + await knex( + tableNames.uploadAdjustmentCatalog + ).insert(sepOctDbInsert); + + const octNovDbInsert: UploadAdjustmentCatalogDBInsert = { + catalog_id: randomUUID(), + adjustment_start_date: new Date("2023-10-15T00:00:00.000Z").toISOString(), + adjustment_end_date: new Date("2023-11-15T00:00:00.000Z").toISOString(), + adjustment_name: "FWD Research October - November '23 Upload Subsidy", + adjustment_description: "A 37.5% discount for uploads", + operator: "multiply", + operator_magnitude: "0.625", + }; + await knex( + tableNames.uploadAdjustmentCatalog + ).insert(octNovDbInsert); + + const novDecDbInsert: UploadAdjustmentCatalogDBInsert = { + catalog_id: randomUUID(), + adjustment_start_date: new Date("2023-11-15T00:00:00.000Z").toISOString(), + adjustment_end_date: new Date("2023-12-15T00:00:00.000Z").toISOString(), + adjustment_name: "FWD Research November - December '23 Upload Subsidy", + adjustment_description: "A 30% discount for uploads", + operator: "multiply", + operator_magnitude: "0.7", + }; + await knex( + tableNames.uploadAdjustmentCatalog + ).insert(novDecDbInsert); + + const decJanDbInsert: UploadAdjustmentCatalogDBInsert = { + catalog_id: randomUUID(), + adjustment_start_date: new Date("2023-12-15T00:00:00.000Z").toISOString(), + adjustment_end_date: new Date("2024-01-15T00:00:00.000Z").toISOString(), + adjustment_name: "FWD Research December '23 - January '24 Upload Subsidy", + adjustment_description: "A 22.5% discount for uploads", + operator: "multiply", + operator_magnitude: "0.775", + }; + await knex( + tableNames.uploadAdjustmentCatalog + ).insert(decJanDbInsert); + + const janFebDbInsert: UploadAdjustmentCatalogDBInsert = { + catalog_id: randomUUID(), + adjustment_start_date: new Date("2024-01-15T00:00:00.000Z").toISOString(), + adjustment_end_date: new Date("2024-02-15T00:00:00.000Z").toISOString(), + adjustment_name: "FWD Research January - February '24 Upload Subsidy", + adjustment_description: "A 15% discount for uploads", + operator: "multiply", + operator_magnitude: "0.85", + }; + await knex( + tableNames.uploadAdjustmentCatalog + ).insert(janFebDbInsert); + + const febMarDbInsert: UploadAdjustmentCatalogDBInsert = { + catalog_id: randomUUID(), + adjustment_start_date: new Date("2024-02-15T00:00:00.000Z").toISOString(), + adjustment_end_date: new Date("2024-03-15T00:00:00.000Z").toISOString(), + adjustment_name: "FWD Research February - March '24 Upload Subsidy", + adjustment_description: "A 7.5% discount for uploads", + operator: "multiply", + operator_magnitude: "0.925", + }; + await knex( + tableNames.uploadAdjustmentCatalog + ).insert(febMarDbInsert); +} + +export async function backfillUploadAdjustments(knex: Knex) { + globalLogger.info("Starting upload adjustment backfill..."); + const backfillStartTime = Date.now(); + + const uploadAdjustmentCatalogs = await knex( + tableNames.uploadAdjustmentCatalog + ); + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const julyAugCatalogId = uploadAdjustmentCatalogs.find( + ({ adjustment_name }) => + adjustment_name === "FWD Research July - August '23 Upload Subsidy" + )!.catalog_id; + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const augSepCatalogId = uploadAdjustmentCatalogs.find( + ({ adjustment_name }) => + adjustment_name === "FWD Research August - September '23 Upload Subsidy" + )!.catalog_id; + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const sepOctCatalogId = uploadAdjustmentCatalogs.find( + ({ adjustment_name }) => + adjustment_name === "FWD Research September - October '23 Upload Subsidy" + )!.catalog_id; + + const balanceReservations = await knex( + tableNames.balanceReservation + ); + + const resIdToAddress = balanceReservations.reduce( + (acc, { reservation_id, user_address }) => { + acc[reservation_id] = user_address; + return acc; + }, + {} as Record + ); + + // Delete all upload adjustments that were created before promo code migration + const uploadAdjustments = await knex( + tableNames.uploadAdjustment + ) + .where("catalog_id", "PRE-PROMO-CODE-MIGRATION-CATALOG-ID") + .delete() + .returning("*"); + + // Setup DB Inserts with new catalog IDs and user addresses + const uploadAdjustmentDbInserts: UploadAdjustmentDBInsert[] = + uploadAdjustments.map( + ({ reservation_id, adjustment_date, ...otherColumns }) => { + const catalog_id = + new Date(adjustment_date) < new Date("2023-08-15T00:00:00.000Z") + ? julyAugCatalogId + : new Date(adjustment_date) < new Date("2023-09-15T00:00:00.000Z") + ? augSepCatalogId + : sepOctCatalogId; + return { + ...otherColumns, + reservation_id, + catalog_id, + user_address: resIdToAddress[reservation_id], + }; + } + ); + + // Batch insert backfilled upload adjustments + await knex.batchInsert("upload_adjustment", uploadAdjustmentDbInserts, 1000); + + // Backfill all receipts and quotes with the new quoted_payment_amount column + const updateQuery = (table: string) => + `update ${table} set quoted_payment_amount = payment_amount where quoted_payment_amount = '0';`; + const tablesToUpdate = [ + "payment_receipt", + "top_up_quote", + "failed_top_up_quote", + "chargeback_receipt", + ]; + await Promise.all( + tablesToUpdate.map((table) => knex.raw(updateQuery(table))) + ); + + globalLogger.info("Finished upload adjustment backfill!", { + backfillMs: Date.now() - backfillStartTime, + }); +} + +export async function addToken2049PromoCodeEvent(knex: Knex): Promise { + const singleUseCatalogDBInsert: SingleUseCodePaymentCatalogDBInsert = { + catalog_id: randomUUID(), + adjustment_name: "Token2049 Singapore Promo Code", + adjustment_description: + "20% off of top up purchase, can be used once per user.", + operator: "multiply", + operator_magnitude: "0.8", + adjustment_exclusivity: "exclusive", + code_value: promoCodeBackfills.welcomeTwentyPercentOff, + // Adjustment start date defaulted to Now() on the migration to dev/prod. For local testing environment, we need to set it to a date in the past + adjustment_start_date: "2023-01-01T00:00:00.000Z", + }; + await knex( + tableNames.singleUseCodePaymentAdjustmentCatalog + ).insert(singleUseCatalogDBInsert); +} + +export const dateOfTwentyThreeFourTurboInfraFeeDeploy = new Date( + "2023-08-23T19:36:00.000Z" +); +export const dateOfTwentyThreePctTurboInfraFeeDeploy = new Date( + "2023-07-13T16:15:00.000Z" +); + +export async function addTurboInfraFee(knex: Knex): Promise { + const turboInfraFeeDBInsert: PaymentAdjustmentCatalogDBInsert = { + catalog_id: randomUUID(), + adjustment_name: "Turbo Infrastructure Fee", + adjustment_description: + "Inclusive usage fee on all payments to cover infrastructure costs and payment provider fees.", + operator: "multiply", + operator_magnitude: "0.766", + adjustment_exclusivity: "inclusive", + adjustment_start_date: + dateOfTwentyThreeFourTurboInfraFeeDeploy.toISOString(), + // No end date as of yet + }; + await knex( + tableNames.paymentAdjustmentCatalog + ).insert(turboInfraFeeDBInsert); + + // Applied From Jul 13 - Aug 23 + const twentyThreePercentInfraFeeDBInsert: PaymentAdjustmentCatalogDBInsert = { + catalog_id: randomUUID(), + adjustment_name: "Turbo Infrastructure Fee", + adjustment_description: + "Inclusive usage fee on all payments to cover infrastructure costs and payment provider fees.", + operator: "multiply", + operator_magnitude: "0.77", + adjustment_exclusivity: "inclusive", + adjustment_start_date: + dateOfTwentyThreePctTurboInfraFeeDeploy.toISOString(), + adjustment_end_date: dateOfTwentyThreeFourTurboInfraFeeDeploy.toISOString(), + }; + await knex( + tableNames.paymentAdjustmentCatalog + ).insert(twentyThreePercentInfraFeeDBInsert); + + // Applied Before Jul 13 + const twentyPercentInfraFeeDBInsert: PaymentAdjustmentCatalogDBInsert = { + catalog_id: randomUUID(), + adjustment_name: "Turbo Infrastructure Fee", + adjustment_description: + "Inclusive usage fee on all payments to cover infrastructure costs and payment provider fees.", + operator: "multiply", + operator_magnitude: "0.8", + adjustment_exclusivity: "inclusive", + adjustment_start_date: "2023-01-01T00:00:00.000Z", // Date before payment service launch + adjustment_end_date: dateOfTwentyThreePctTurboInfraFeeDeploy.toISOString(), + }; + await knex( + tableNames.paymentAdjustmentCatalog + ).insert(twentyPercentInfraFeeDBInsert); +} + +async function findInfraFeeCatalogIds( + knex: Knex +): Promise<[string, string, string]> { + const turboInfraFeeAdjustmentCatalogs = + await knex( + "payment_adjustment_catalog" + ).where({ + adjustment_name: "Turbo Infrastructure Fee", + }); + + const catalogIdForTwentyPctFee = turboInfraFeeAdjustmentCatalogs.find( + ({ operator_magnitude }) => operator_magnitude === "0.8" + )?.catalog_id; + const catalogIdForTwentyThreePctFee = turboInfraFeeAdjustmentCatalogs.find( + ({ operator_magnitude }) => operator_magnitude === "0.77" + )?.catalog_id; + const catalogIdForTwentyThreeFourPctFee = + turboInfraFeeAdjustmentCatalogs.find( + ({ operator_magnitude }) => operator_magnitude === "0.766" + )?.catalog_id; + + if ( + catalogIdForTwentyPctFee === undefined || + catalogIdForTwentyThreePctFee === undefined || + catalogIdForTwentyThreeFourPctFee === undefined + ) { + throw new Error("Could not find Turbo Infra Fee Adjustment Catalogs"); + } + + return [ + catalogIdForTwentyPctFee, + catalogIdForTwentyThreePctFee, + catalogIdForTwentyThreeFourPctFee, + ]; +} + +export async function backfillTurboInfraFee(knex: Knex) { + const [ + catalogIdForTwentyPctFee, + catalogIdForTwentyThreePctFee, + catalogIdForTwentyThreeFourPctFee, + ] = await findInfraFeeCatalogIds(knex); + + const paymentReceipts = await knex("payment_receipt"); + + // Catch any Turbo Infra Fee Adjustments added between code deploy and backfill + const overlappedAdjustments = await knex( + "payment_adjustment" + ).where({ catalog_id: catalogIdForTwentyThreeFourPctFee }); + + const paymentReceiptsToBackfill = paymentReceipts.filter( + ({ top_up_quote_id }) => + !overlappedAdjustments.some( + ({ top_up_quote_id: overlappedTopUpQuoteId }) => + overlappedTopUpQuoteId === top_up_quote_id + ) + ); + + const paymentAdjustments: PaymentAdjustmentDBInsert[] = + paymentReceiptsToBackfill.map( + ({ + top_up_quote_id, + payment_amount, + quote_creation_date, + destination_address, + quoted_payment_amount, + }) => { + const quoteDate = new Date(quote_creation_date); + + const [multiplier, catalog_id] = + quoteDate < dateOfTwentyThreePctTurboInfraFeeDeploy + ? [0.2, catalogIdForTwentyPctFee] + : quoteDate < dateOfTwentyThreePctTurboInfraFeeDeploy + ? [0.23, catalogIdForTwentyThreePctFee] + : [0.234, catalogIdForTwentyThreeFourPctFee]; + + const adjustmentAmount = new Winston(payment_amount).times(multiplier); + + return { + catalog_id, + top_up_quote_id, + adjusted_payment_amount: `-${adjustmentAmount.toString()}`, + adjusted_currency_type: "usd", + // If the payment amount is the same as the quoted payment amount, then this is the first and only adjustment + // Otherwise the TOKEN2049 promo code was applied and this is the second adjustment + adjustment_index: payment_amount === quoted_payment_amount ? 0 : 1, + adjustment_date: quote_creation_date, + user_address: destination_address, + }; + } + ); + + await knex.batchInsert( + "payment_adjustment", + paymentAdjustments + ); +} + +export async function rollbackInfraFeeBackfill(knex: Knex) { + const infraFeeCatalogIds = await findInfraFeeCatalogIds(knex); + + await knex("payment_adjustment") + .whereIn("catalog_id", infraFeeCatalogIds) + .del(); +} diff --git a/src/database/migrator.ts b/src/database/migrator.ts new file mode 100644 index 0000000..881f0bb --- /dev/null +++ b/src/database/migrator.ts @@ -0,0 +1,233 @@ +/** + * Copyright (C) 2022-2023 Permanent Data Solutions, Inc. All Rights Reserved. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +import { Knex } from "knex"; +import { randomUUID } from "node:crypto"; + +import globalLogger from "../logger"; +import { columnNames, tableNames } from "./dbConstants"; +import { SingleUseCodePaymentCatalogDBInsert } from "./dbTypes"; +import { backfillTurboInfraFee, rollbackInfraFeeBackfill } from "./migration"; + +export abstract class Migrator { + protected async operate({ + name, + operation, + }: { + name: string; + operation: () => Promise; + }) { + globalLogger.info(`Starting ${name}...`); + const startTime = Date.now(); + + await operation(); + + globalLogger.info(`Finished ${name}!`, { + durationMs: Date.now() - startTime, + }); + } + + abstract migrate(): Promise; + abstract rollback(): Promise; +} + +export class BackfillInfraFeeMigrator extends Migrator { + constructor(private readonly knex: Knex) { + super(); + } + + public migrate() { + return this.operate({ + name: "migrate to backfill infra fee", + operation: () => backfillTurboInfraFee(this.knex), + }); + } + + public rollback() { + return this.operate({ + name: "rollback from infra fee backfill", + operation: () => rollbackInfraFeeBackfill(this.knex), + }); + } +} + +export class PilotReferralMigrator extends Migrator { + constructor(private readonly knex: Knex) { + super(); + } + + public migrate() { + return this.operate({ + name: "migrate to pilot referral", + operation: () => + this.knex.schema.alterTable( + tableNames.singleUseCodePaymentAdjustmentCatalog, + (table) => { + table.integer(columnNames.maxUses).notNullable().defaultTo(0); + table + .integer(columnNames.minimumPaymentAmount) + .notNullable() + .defaultTo(0); + } + ), + }); + } + + public rollback() { + return this.operate({ + name: "rollback from pilot referral", + operation: () => + this.knex.schema.alterTable( + tableNames.singleUseCodePaymentAdjustmentCatalog, + (table) => { + table.dropColumn(columnNames.maxUses); + table.dropColumn(columnNames.minimumPaymentAmount); + } + ), + }); + } +} + +export class MaxDiscountMigrator extends Migrator { + constructor(private readonly knex: Knex) { + super(); + } + + public migrate() { + return this.operate({ + name: "migrate to max discount", + operation: async () => { + await this.knex.schema.alterTable( + tableNames.singleUseCodePaymentAdjustmentCatalog, + (table) => { + table + .integer(columnNames.maximumDiscountAmount) + .notNullable() + .defaultTo(0); + } + ); + const pilot50DbInsert: SingleUseCodePaymentCatalogDBInsert = { + adjustment_name: "Pilot-50 2023 Promo Code", + adjustment_description: "50% off for new users", + operator: "multiply", + operator_magnitude: "0.5", + target_user_group: "new", + catalog_id: randomUUID(), + code_value: "PILOT50", + adjustment_exclusivity: "exclusive", + maximum_discount_amount: 10_00, + }; + await this.knex( + tableNames.singleUseCodePaymentAdjustmentCatalog + ).insert(pilot50DbInsert); + }, + }); + } + + public rollback() { + return this.operate({ + name: "rollback from max discount", + operation: async () => { + await this.knex.schema.alterTable( + tableNames.singleUseCodePaymentAdjustmentCatalog, + (table) => { + table.dropColumn(columnNames.maximumDiscountAmount); + } + ); + await this.knex(tableNames.singleUseCodePaymentAdjustmentCatalog) + .where({ code_value: "PILOT50" }) + .del(); + }, + }); + } +} + +export class GiftByEmailMigrator extends Migrator { + constructor(private readonly knex: Knex) { + super(); + } + + private topUpQuoteTableNames = [ + tableNames.topUpQuote, + tableNames.paymentReceipt, + tableNames.failedTopUpQuote, + tableNames.chargebackReceipt, + ]; + + public migrate() { + return this.operate({ + name: "migrate to gift by email", + operation: async () => { + await Promise.all( + this.topUpQuoteTableNames.map((table) => + this.knex.schema.alterTable(table, (table) => { + table.string(columnNames.giftMessage).nullable(); + }) + ) + ); + + await this.knex.schema.createTable( + tableNames.unredeemedGift, + (table) => { + table.string(columnNames.paymentReceiptId).primary(); + table.string(columnNames.recipientEmail).notNullable(); + table + .timestamp(columnNames.creationDate) + .notNullable() + .defaultTo(this.knex.fn.now()); + table + .timestamp(columnNames.expirationDate) + .notNullable() + .defaultTo(this.knex.raw("now() + interval '1 year'")); + table.string(columnNames.giftedWincAmount).notNullable(); + table.string(columnNames.giftMessage).nullable(); + table.string(columnNames.senderEmail).nullable(); + } + ); + + await this.knex.schema.createTableLike( + tableNames.redeemedGift, + tableNames.unredeemedGift, + (table) => { + table + .timestamp(columnNames.redemptionDate) + .notNullable() + .defaultTo(this.knex.fn.now()); + table.string(columnNames.destinationAddress).notNullable(); + } + ); + }, + }); + } + + public rollback() { + return this.operate({ + name: "rollback from gift by email", + operation: async () => { + await Promise.all( + this.topUpQuoteTableNames.map((table) => + this.knex.schema.alterTable(table, (table) => { + table.dropColumn(columnNames.giftMessage); + }) + ) + ); + + await this.knex.schema.dropTable(tableNames.unredeemedGift); + await this.knex.schema.dropTable(tableNames.redeemedGift); + }, + }); + } +} diff --git a/src/database/postgres.ts b/src/database/postgres.ts index 71ce682..d20af7b 100644 --- a/src/database/postgres.ts +++ b/src/database/postgres.ts @@ -14,7 +14,9 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -import knexConstructor, { Knex } from "knex"; +import { randomUUID } from "crypto"; +import knex, { Knex } from "knex"; +import path from "path"; import winston from "winston"; import logger from "../logger"; @@ -23,44 +25,89 @@ import { Database } from "./database"; import { columnNames, tableNames } from "./dbConstants"; import { chargebackReceiptDBMap, + paymentAdjustmentCatalogDBMap, paymentReceiptDBMap, + singleUseCodePaymentCatalogDBMap, topUpQuoteDBMap, + unredeemedGiftDBMap, + uploadAdjustmentCatalogDBMap, userDBMap, } from "./dbMaps"; import { AuditLogInsert, + BalanceReservationDBInsert, + BalanceReservationDBResult, ChargebackReceipt, ChargebackReceiptDBResult, + CreateBalanceReservationParams, CreateChargebackReceiptParams, CreatePaymentReceiptParams, CreateTopUpQuoteParams, FailedTopUpQuoteDBResult, + PaymentAdjustmentCatalog, + PaymentAdjustmentCatalogDBResult, + PaymentAdjustmentDBInsert, + PaymentAdjustmentDBResult, PaymentReceipt, PaymentReceiptDBResult, PromotionalInfo, + RedeemedGiftDBInsert, + RedeemedGiftDBResult, + SingleUseCodePaymentCatalog, + SingleUseCodePaymentCatalogDBResult, TopUpQuote, + TopUpQuoteDBInsert, TopUpQuoteDBResult, + UnredeemedGift, + UnredeemedGiftDBInsert, + UnredeemedGiftDBResult, + UploadAdjustmentCatalog, + UploadAdjustmentCatalogDBResult, + UploadAdjustmentDBInsert, User, + UserDBInsert, UserDBResult, } from "./dbTypes"; -import { InsufficientBalance, UserNotFoundWarning } from "./errors"; -import { getWriterConfig } from "./knexConfig"; -import { getReaderConfig } from "./knexConfig"; +import { + GiftAlreadyRedeemed, + GiftRedemptionError, + InsufficientBalance, + PromoCodeExceedsMaxUses, + PromoCodeExpired, + PromoCodeNotFound, + UserIneligibleForPromoCode, + UserNotFoundWarning, +} from "./errors"; +import { getReaderConfig, getWriterConfig } from "./knexConfig"; export class PostgresDatabase implements Database { private log: winston.Logger; - private knexWriter: Knex; - private knexReader: Knex; - - constructor(knexWriter?: Knex, knexReader?: Knex) { + private writer: Knex; + private reader: Knex; + + constructor({ + writer = knex(getWriterConfig()), + reader = knex(getReaderConfig()), + migrate = false, + }: { + writer?: Knex; + reader?: Knex; + migrate?: boolean; + } = {}) { this.log = logger.child({ class: this.constructor.name }); - /** Knex instance connected to a PostgreSQL database */ - const pgWriter = knexConstructor(getWriterConfig()); - const pgReader = knexConstructor(getReaderConfig()); + this.writer = writer; + this.reader = reader; - this.knexWriter = knexWriter ?? pgWriter; - this.knexReader = knexReader ?? pgReader; + if (migrate) { + this.log.info("Migrating database..."); + this.writer.migrate + .latest({ directory: path.join(__dirname, "../migrations") }) + .then(() => this.log.info("Database migration complete.")) + .catch((error) => { + this.log.error("Failed to migrate database!", error); + }); + } } public async createTopUpQuote( @@ -72,6 +119,8 @@ export class PostgresDatabase implements Database { const { paymentAmount, + quotedPaymentAmount, + adjustments, currencyType, destinationAddress, destinationAddressType, @@ -79,10 +128,12 @@ export class PostgresDatabase implements Database { quoteExpirationDate, topUpQuoteId, winstonCreditAmount, + giftMessage, } = topUpQuote; - await this.knexWriter(tableNames.topUpQuote).insert({ + const topUpQuoteDbInsert: TopUpQuoteDBInsert = { payment_amount: paymentAmount.toString(), + quoted_payment_amount: quotedPaymentAmount.toString(), currency_type: currencyType, destination_address: destinationAddress, destination_address_type: destinationAddressType, @@ -90,11 +141,33 @@ export class PostgresDatabase implements Database { quote_expiration_date: quoteExpirationDate, top_up_quote_id: topUpQuoteId, winston_credit_amount: winstonCreditAmount.toString(), + gift_message: giftMessage, + }; + + await this.writer.transaction(async (knexTransaction) => { + await knexTransaction(tableNames.topUpQuote).insert( + topUpQuoteDbInsert + ); + + await knexTransaction.batchInsert( + tableNames.paymentAdjustment, + adjustments.map(({ adjustmentAmount, catalogId }, index) => { + const adjustmentDbInsert: PaymentAdjustmentDBInsert = { + adjusted_payment_amount: adjustmentAmount.toString(), + adjusted_currency_type: currencyType, + user_address: destinationAddress, + catalog_id: catalogId, + adjustment_index: index, + top_up_quote_id: topUpQuoteId, + }; + return adjustmentDbInsert; + }) + ); }); } public async getTopUpQuote(topUpQuoteId: string): Promise { - const topUpQuoteDbResult = await this.knexReader( + const topUpQuoteDbResult = await this.reader( tableNames.topUpQuote ).where({ [columnNames.topUpQuoteId]: topUpQuoteId, @@ -112,7 +185,7 @@ export class PostgresDatabase implements Database { userAddress: string, promoInfo: PromotionalInfo ): Promise { - await this.knexWriter.transaction(async (knexTransaction) => { + await this.writer.transaction(async (knexTransaction) => { await this.getUser(userAddress, knexTransaction); await knexTransaction(tableNames.user) @@ -131,7 +204,7 @@ export class PostgresDatabase implements Database { public async getUser( userAddress: string, - knexTransaction: Knex.Transaction = this.knexReader as Knex.Transaction + knexTransaction: Knex.Transaction = this.reader as Knex.Transaction ): Promise { const userDbResult = await knexTransaction( tableNames.user @@ -147,20 +220,26 @@ export class PostgresDatabase implements Database { } public async getBalance(userAddress: string): Promise { + // TODO: getBalance should be the result of current_winc_balance - all pending balance_reservation.reserved_winc_amount once finalized_reservations are implemented return (await this.getUser(userAddress)).winstonCreditBalance; } public async createPaymentReceipt( paymentReceipt: CreatePaymentReceiptParams - ): Promise { + ): Promise { this.log.info("Inserting new payment receipt...", { paymentReceipt, }); - const { topUpQuoteId, paymentReceiptId, paymentAmount, currencyType } = - paymentReceipt; + const { + topUpQuoteId, + paymentReceiptId, + paymentAmount, + currencyType, + senderEmail, + } = paymentReceipt; - await this.knexWriter.transaction(async (knexTransaction) => { + return this.writer.transaction(async (knexTransaction) => { const topUpQuoteDbResults = await knexTransaction( tableNames.topUpQuote ).where({ @@ -193,6 +272,35 @@ export class PostgresDatabase implements Database { ); } + // Check adjustment eligibility + const paymentAdjustmentDbResults = + await knexTransaction( + tableNames.paymentAdjustment + ).where({ + top_up_quote_id: topUpQuoteId, + }); + + for (const { catalog_id } of paymentAdjustmentDbResults) { + // Check the single use promo code table for any matching adjustment catalogs + const singleUseAdjustmentCatalog = + await knexTransaction( + tableNames.singleUseCodePaymentAdjustmentCatalog + ) + .where({ + catalog_id, + }) + .first(); + + if (singleUseAdjustmentCatalog !== undefined) { + // If one is found, check if the user is still eligible for this promo code since the quote was created + await this.assertCodeEligibility( + destination_address, + singleUseAdjustmentCatalog, + knexTransaction + ); + } + } + // Delete top up quote const topUpQuote = await knexTransaction( tableNames.topUpQuote @@ -208,66 +316,236 @@ export class PostgresDatabase implements Database { tableNames.paymentReceipt ).insert({ ...topUpQuote[0], payment_receipt_id: paymentReceiptId }); + if (destination_address_type === "email") { + const unredeemedGiftDbInsert: UnredeemedGiftDBInsert = { + recipient_email: destination_address, + payment_receipt_id: paymentReceiptId, + gifted_winc_amount: winston_credit_amount, + gift_message: topUpQuote[0].gift_message, + sender_email: senderEmail, + }; + const unredeemedGiftDbResult = + await knexTransaction( + tableNames.unredeemedGift + ) + .insert(unredeemedGiftDbInsert) + .returning("*"); + + const auditLogInsert: AuditLogInsert = { + user_address: destination_address, + winston_credit_amount: "0", + change_reason: "gifted_payment", + change_id: paymentReceiptId, + }; + await knexTransaction(tableNames.auditLog).insert(auditLogInsert); + + return unredeemedGiftDbResult.map(unredeemedGiftDBMap)[0]; + } else { + const destinationUser = ( + await knexTransaction(tableNames.user).where({ + user_address: destination_address, + }) + )[0]; + + if (destinationUser === undefined) { + this.log.info("No existing user was found; creating new user...", { + userAddress: destination_address, + newBalance: winston_credit_amount, + paymentReceipt, + }); + await knexTransaction(tableNames.user).insert({ + user_address: destination_address, + user_address_type: destination_address_type, + winston_credit_balance: winston_credit_amount, + }); + + const auditLogInsert: AuditLogInsert = { + user_address: destination_address, + winston_credit_amount, + change_reason: "account_creation", + change_id: paymentReceiptId, + }; + await knexTransaction(tableNames.auditLog).insert(auditLogInsert); + } else { + // Increment balance of existing user + const currentBalance = new Winston( + destinationUser.winston_credit_balance + ); + const newBalance = currentBalance.plus( + new Winston(winston_credit_amount) + ); + + this.log.info("Incrementing balance...", { + userAddress: destination_address, + currentBalance, + newBalance, + paymentReceipt, + }); + + await knexTransaction(tableNames.user) + .where({ + user_address: destination_address, + }) + .update({ winston_credit_balance: newBalance.toString() }); + + const auditLogInsert: AuditLogInsert = { + user_address: destination_address, + winston_credit_amount, + change_reason: "payment", + change_id: paymentReceiptId, + }; + await knexTransaction(tableNames.auditLog).insert(auditLogInsert); + } + return; + } + }); + } + + public async redeemGift({ + destinationAddress, + paymentReceiptId, + recipientEmail, + }: { + paymentReceiptId: string; + recipientEmail: string; + destinationAddress: string; + }): Promise { + return this.writer.transaction(async (knexTransaction) => { + const unredeemedGiftDbResults = + await knexTransaction( + tableNames.unredeemedGift + ).where({ + payment_receipt_id: paymentReceiptId, + }); + + if (unredeemedGiftDbResults.length === 0) { + logger.warn( + `No unredeemed gift found in database with payment receipt ID '${paymentReceiptId}'` + ); + + const redeemedDbResults = await knexTransaction( + tableNames.redeemedGift + ).where({ + payment_receipt_id: paymentReceiptId, + }); + if (redeemedDbResults.length > 0) { + logger.warn( + `Payment receipt ID '${paymentReceiptId}' has already been redeemed!` + ); + throw new GiftAlreadyRedeemed(); + } + + throw new GiftRedemptionError(); + } + + const paymentReceiptDbResults = + await knexTransaction( + tableNames.paymentReceipt + ).where({ + payment_receipt_id: paymentReceiptId, + }); + + if (paymentReceiptDbResults.length === 0) { + logger.warn( + `No payment receipt found in database with payment receipt ID '${paymentReceiptId}'` + ); + throw new GiftRedemptionError(); + } + + const unredeemedGiftDbResult = unredeemedGiftDbResults[0]; + + if (unredeemedGiftDbResult.recipient_email !== recipientEmail) { + logger.warn( + `Recipient email '${recipientEmail}' does not match unredeemed gift recipient email '${unredeemedGiftDbResult.recipient_email}'` + ); + throw new GiftRedemptionError(); + } + + const redeemedGiftDbInsert: RedeemedGiftDBInsert = { + ...unredeemedGiftDbResult, + destination_address: destinationAddress, + }; + + await knexTransaction(tableNames.unredeemedGift) + .where({ + payment_receipt_id: paymentReceiptId, + }) + .del(); + + await knexTransaction(tableNames.redeemedGift).insert( + redeemedGiftDbInsert + ); + const destinationUser = ( await knexTransaction(tableNames.user).where({ - user_address: destination_address, + user_address: destinationAddress, }) )[0]; + if (destinationUser === undefined) { this.log.info("No existing user was found; creating new user...", { - userAddress: destination_address, - newBalance: winston_credit_amount, - paymentReceipt, - }); - await knexTransaction(tableNames.user).insert({ - user_address: destination_address, - user_address_type: destination_address_type, - winston_credit_balance: winston_credit_amount, + userAddress: destinationAddress, + newBalance: unredeemedGiftDbResult.gifted_winc_amount, }); + const userDbInsert: UserDBInsert = { + user_address: destinationAddress, + user_address_type: "arweave", + winston_credit_balance: unredeemedGiftDbResult.gifted_winc_amount, + }; + const userDbResult = await knexTransaction( + tableNames.user + ) + .insert(userDbInsert) + .returning("*"); const auditLogInsert: AuditLogInsert = { - user_address: destination_address, - winston_credit_amount, - change_reason: "account_creation", + user_address: destinationAddress, + winston_credit_amount: unredeemedGiftDbResult.gifted_winc_amount, + change_reason: "gifted_account_creation", change_id: paymentReceiptId, }; await knexTransaction(tableNames.auditLog).insert(auditLogInsert); + return userDbResult.map(userDBMap)[0]; } else { // Increment balance of existing user const currentBalance = new Winston( destinationUser.winston_credit_balance ); const newBalance = currentBalance.plus( - new Winston(winston_credit_amount) + new Winston(unredeemedGiftDbResult.gifted_winc_amount) ); this.log.info("Incrementing balance...", { - userAddress: destination_address, + userAddress: destinationAddress, currentBalance, newBalance, - paymentReceipt, }); - await knexTransaction(tableNames.user) + const userDbResult = await knexTransaction( + tableNames.user + ) .where({ - user_address: destination_address, + user_address: destinationAddress, }) - .update({ winston_credit_balance: newBalance.toString() }); + .update({ winston_credit_balance: newBalance.toString() }) + .returning("*"); const auditLogInsert: AuditLogInsert = { - user_address: destination_address, - winston_credit_amount, - change_reason: "payment", + user_address: destinationAddress, + winston_credit_amount: unredeemedGiftDbResult.gifted_winc_amount, + change_reason: "gifted_payment_redemption", change_id: paymentReceiptId, }; await knexTransaction(tableNames.auditLog).insert(auditLogInsert); + + return userDbResult.map(userDBMap)[0]; } }); } public async getPaymentReceipt( paymentReceiptId: string, - knexTransaction: Knex.Transaction = this.knexReader as Knex.Transaction + knexTransaction: Knex.Transaction = this.reader as Knex.Transaction ): Promise { return this.getPaymentReceiptWhere( { [columnNames.paymentReceiptId]: paymentReceiptId }, @@ -277,7 +555,7 @@ export class PostgresDatabase implements Database { private async getPaymentReceiptByTopUpQuoteId( topUpQuoteId: string, - knexTransaction: Knex.Transaction = this.knexReader as Knex.Transaction + knexTransaction: Knex.Transaction = this.reader as Knex.Transaction ): Promise { return this.getPaymentReceiptWhere( { [columnNames.topUpQuoteId]: topUpQuoteId }, @@ -287,7 +565,7 @@ export class PostgresDatabase implements Database { private async getPaymentReceiptWhere( where: Partial, - knexTransaction: Knex.Transaction = this.knexReader as Knex.Transaction + knexTransaction: Knex.Transaction = this.reader as Knex.Transaction ): Promise { const paymentReceiptDbResults = await knexTransaction( @@ -307,7 +585,7 @@ export class PostgresDatabase implements Database { private async getChargebackReceiptWhere( where: Partial, - knexTransaction: Knex.Transaction = this.knexReader as Knex.Transaction + knexTransaction: Knex.Transaction = this.reader as Knex.Transaction ): Promise { const chargebackReceiptDbResult = await knexTransaction( @@ -334,39 +612,72 @@ export class PostgresDatabase implements Database { topUpQuoteId, }); - await this.knexWriter.transaction(async (knexTransaction) => { + await this.writer.transaction(async (knexTransaction) => { // This will throw if payment receipt does not exist const { destinationAddress, paymentReceiptId, winstonCreditAmount: winstonClawbackAmount, + destinationAddressType, } = await this.getPaymentReceiptByTopUpQuoteId( topUpQuoteId, knexTransaction ); - const user = await this.getUser(destinationAddress, knexTransaction); + let userAddress: string | undefined; + + if (destinationAddressType === "email") { + const redeemedGiftDbResults = + await knexTransaction( + tableNames.unredeemedGift + ).where({ + payment_receipt_id: paymentReceiptId, + }); + + if (redeemedGiftDbResults.length === 0) { + // When no redeemed exists yet, delete the unredeemed gift and leave user address undefined + await knexTransaction( + tableNames.unredeemedGift + ) + .where({ + payment_receipt_id: paymentReceiptId, + }) + .del(); + } else { + userAddress = redeemedGiftDbResults[0].destination_address; + } + } else { + userAddress = destinationAddress; + } - // Decrement balance of existing user - const currentBalance = user.winstonCreditBalance; + if (userAddress) { + const user = await this.getUser(userAddress, knexTransaction); - // this could result in a negative balance for a user, will throw an error if non-integer winston balance - const newBalance = currentBalance.minus(winstonClawbackAmount); + // Decrement balance of existing user + const currentBalance = user.winstonCreditBalance; - // Update the users balance. - await knexTransaction(tableNames.user) - .where({ - user_address: destinationAddress, - }) - .update({ winston_credit_balance: newBalance.toString() }); + // this could result in a negative balance for a user, will throw an error if non-integer winston balance + const newBalance = currentBalance.minus(winstonClawbackAmount); - const auditLogInsert: AuditLogInsert = { - user_address: destinationAddress, - winston_credit_amount: `-${winstonClawbackAmount.toString()}`, // a negative value because this amount was withdrawn from the users balance - change_reason: "chargeback", - change_id: chargebackReceiptId, - }; - await knexTransaction(tableNames.auditLog).insert(auditLogInsert); + // Update the users balance. + await knexTransaction(tableNames.user) + .where({ + user_address: destinationAddress, + }) + .update({ winston_credit_balance: newBalance.toString() }); + + const auditLogInsert: AuditLogInsert = { + user_address: destinationAddress, + winston_credit_amount: `-${winstonClawbackAmount.toString()}`, // a negative value because this amount was withdrawn from the users balance + change_reason: "chargeback", + change_id: chargebackReceiptId, + }; + await knexTransaction(tableNames.auditLog).insert(auditLogInsert); + } else { + logger.warn( + `Chargeback receipt created for payment receipt ID '${paymentReceiptId}' but user has not redeemed gift yet!` + ); + } // Remove from payment receipt table, const paymentReceiptDbResult = @@ -390,7 +701,7 @@ export class PostgresDatabase implements Database { chargebackReceiptId: string ): Promise { const chargebackReceiptDbResult = - await this.knexReader( + await this.reader( tableNames.chargebackReceipt ).where({ [columnNames.chargebackReceiptId]: chargebackReceiptId, @@ -404,22 +715,53 @@ export class PostgresDatabase implements Database { return chargebackReceiptDbResult.map(chargebackReceiptDBMap)[0]; } - public async reserveBalance( - userAddress: string, - winstonCreditAmount: Winston, - dataItemId?: TransactionId - ): Promise { - await this.knexWriter.transaction(async (knexTransaction) => { + public async reserveBalance({ + userAddress, + networkWincAmount, + reservedWincAmount, + dataItemId, + adjustments = [], + }: CreateBalanceReservationParams): Promise { + await this.writer.transaction(async (knexTransaction) => { const user = await this.getUser(userAddress, knexTransaction); const currentWinstonBalance = user.winstonCreditBalance; - const newBalance = currentWinstonBalance.minus(winstonCreditAmount); + const newBalance = currentWinstonBalance.minus(reservedWincAmount.winc); // throw insufficient balance error if the user would go to a negative balance if (newBalance.isNonZeroNegativeInteger()) { throw new InsufficientBalance(userAddress); } + const reservationId = randomUUID(); + + const balanceReservationDbInsert: BalanceReservationDBInsert = { + reservation_id: reservationId, + data_item_id: dataItemId, + reserved_winc_amount: reservedWincAmount.toString(), + network_winc_amount: networkWincAmount.toString(), + user_address: userAddress, + }; + + await knexTransaction( + tableNames.balanceReservation + ).insert(balanceReservationDbInsert); + + await knexTransaction.batchInsert( + tableNames.uploadAdjustment, + adjustments.map(({ adjustmentAmount, catalogId }, index) => { + const adjustmentDbInsert: UploadAdjustmentDBInsert = { + adjusted_winc_amount: adjustmentAmount.toString(), + user_address: userAddress, + catalog_id: catalogId, + adjustment_index: index, + reservation_id: reservationId, + }; + return adjustmentDbInsert; + }) + ); + + // TODO: Move this decrement balance onto finalized_reservation once implemented await knexTransaction(tableNames.user) .where({ user_address: userAddress, @@ -428,7 +770,7 @@ export class PostgresDatabase implements Database { const auditLogInsert: AuditLogInsert = { user_address: userAddress, - winston_credit_amount: `-${winstonCreditAmount.toString()}`, // a negative value because this amount was withdrawn from the users balance + winston_credit_amount: `-${reservedWincAmount.toString()}`, // a negative value because this amount was withdrawn from the users balance change_reason: "upload", change_id: dataItemId, }; @@ -441,7 +783,7 @@ export class PostgresDatabase implements Database { winstonCreditAmount: Winston, dataItemId?: TransactionId ): Promise { - await this.knexWriter.transaction(async (knexTransaction) => { + await this.writer.transaction(async (knexTransaction) => { const user = await this.getUser(userAddress, knexTransaction); const currentWinstonBalance = user.winstonCreditBalance; @@ -466,7 +808,7 @@ export class PostgresDatabase implements Database { public async checkForExistingPaymentByTopUpQuoteId( top_up_quote_id: string ): Promise { - return this.knexReader.transaction(async (knexTransaction) => { + return this.reader.transaction(async (knexTransaction) => { const [ paymentReceiptResult, chargebackReceiptResult, @@ -492,4 +834,176 @@ export class PostgresDatabase implements Database { ); }); } + + public async getUploadAdjustmentCatalogs(): Promise< + UploadAdjustmentCatalog[] + > { + const currentDate = new Date().toISOString(); + return ( + await this.reader( + tableNames.uploadAdjustmentCatalog + ) + .whereRaw( + `'${currentDate}' >= ${columnNames.adjustmentStartDate} and ( + ${columnNames.adjustmentEndDate} is null or '${currentDate}' < ${columnNames.adjustmentEndDate} + )` + ) + .orderBy(columnNames.adjustmentPriority, "asc") + ).map(uploadAdjustmentCatalogDBMap); + } + + public async getPaymentAdjustmentCatalogs(): Promise< + PaymentAdjustmentCatalog[] + > { + const currentDate = new Date().toISOString(); + return ( + await this.reader( + tableNames.paymentAdjustmentCatalog + ) + .whereRaw( + `'${currentDate}' >= ${columnNames.adjustmentStartDate} and ( + ${columnNames.adjustmentEndDate} is null or '${currentDate}' < ${columnNames.adjustmentEndDate} + )` + ) + .orderBy(columnNames.adjustmentPriority, "asc") + ).map(paymentAdjustmentCatalogDBMap); + } + + private async checkForSingleUsePromoCodeEligibility( + userAddress: string, + catalogId: string, + knexTransaction: Knex.Transaction + ): Promise { + const existingAdjustments = + await knexTransaction( + tableNames.paymentAdjustment + ).where({ + user_address: userAddress, + catalog_id: catalogId, + }); + + const existingPaymentReceiptPromises = existingAdjustments.map( + ({ top_up_quote_id }) => + knexTransaction( + tableNames.paymentReceipt + ).where({ + top_up_quote_id, + }) + ); + + const existingPaymentReceiptDbResults = await Promise.all( + existingPaymentReceiptPromises + ); + + return existingPaymentReceiptDbResults.every( + (paymentReceiptDbResults) => paymentReceiptDbResults.length === 0 + ); + } + + private async checkForNewUsersCodeEligibility( + userAddress: string, + knexTransaction: Knex.Transaction + ): Promise { + const existingPaymentReceipts = + await knexTransaction( + tableNames.paymentReceipt + ).where({ + destination_address: userAddress, + }); + + return existingPaymentReceipts.length === 0; + } + + private async assertCodeEligibility( + userAddress: string, + { + catalog_id, + code_value, + target_user_group, + max_uses, + adjustment_end_date, + }: SingleUseCodePaymentCatalogDBResult, + knexTransaction: Knex.Transaction + ): Promise { + if (adjustment_end_date && new Date() > new Date(adjustment_end_date)) { + throw new PromoCodeExpired(code_value, adjustment_end_date); + } + + // 0 max uses means unlimited + if (max_uses > 0) { + const existingAdjustments = + await knexTransaction( + tableNames.paymentAdjustment + ).where({ + catalog_id, + }); + + if (existingAdjustments.length >= max_uses) { + throw new PromoCodeExceedsMaxUses(code_value, max_uses); + } + } + const isEligible = + target_user_group === "new" + ? await this.checkForNewUsersCodeEligibility( + userAddress, + knexTransaction + ) + : await this.checkForSingleUsePromoCodeEligibility( + userAddress, + catalog_id, + knexTransaction + ); + if (!isEligible) { + throw new UserIneligibleForPromoCode(userAddress, code_value); + } + } + + public async getSingleUsePromoCodeAdjustments( + promoCodes: string[], + userAddress: string + ): Promise { + if (promoCodes.length === 0) { + return []; + } + return this.reader.transaction(async (knexTransaction) => { + const currentDate = new Date().toISOString(); + const promoCodeAdjustments = + await knexTransaction( + tableNames.singleUseCodePaymentAdjustmentCatalog + ) + .whereRaw(`'${currentDate}' >= ${columnNames.adjustmentStartDate}`) + .orderBy(columnNames.adjustmentPriority, "asc"); + + const qualifiedAdjustments: SingleUseCodePaymentCatalogDBResult[] = []; + + for (let i = 0; i < promoCodes.length; i++) { + const code = promoCodes[i]; + + const matchingAdjustments = promoCodeAdjustments.filter( + (a) => a.code_value === code + ); + if (matchingAdjustments.length === 0) { + throw new PromoCodeNotFound(code); + } + + // When matching multiple codes, only use and validate against the adjustment with the most recent start date + const promoCodeAdjustment = matchingAdjustments.sort((a, b) => { + return ( + new Date(b.adjustment_start_date).getTime() - + new Date(a.adjustment_start_date).getTime() + ); + })[0]; + + await this.assertCodeEligibility( + userAddress, + promoCodeAdjustment, + knexTransaction + ); + + qualifiedAdjustments.push(promoCodeAdjustment); + } + + return qualifiedAdjustments.map(singleUseCodePaymentCatalogDBMap); + }); + } } diff --git a/src/database/schema.ts b/src/database/schema.ts index de27ce5..7595f41 100644 --- a/src/database/schema.ts +++ b/src/database/schema.ts @@ -14,11 +14,24 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ +import { randomUUID } from "crypto"; import { Knex } from "knex"; import logger from "../logger"; import { columnNames, tableNames } from "./dbConstants"; -import { AuditChangeReason, AuditLogDBResult } from "./dbTypes"; +import { + AuditChangeReason, + AuditLogDBResult, + SingleUseCodePaymentCatalogDBInsert, + SingleUseCodePaymentCatalogDBResult, +} from "./dbTypes"; +import { + addFwdResearchSubsidyUploadCatalogs, + addToken2049PromoCodeEvent, + addTurboInfraFee, + backfillBalanceReservations, + backfillUploadAdjustments, +} from "./migration"; export class Schema { private constructor(private readonly pg: Knex) {} @@ -53,6 +66,397 @@ export class Schema { ).rollBackFromMigrateAuditLogToPositiveNegativeCredits(); } + public static migrateToBalanceReservation(pg: Knex): Promise { + return new Schema(pg).migrateToBalanceReservation(); + } + + public static rollbackFromBalanceReservation(pg: Knex): Promise { + return new Schema(pg).rollbackFromBalanceReservation(); + } + private async migrateToBalanceReservation() { + logger.info("Starting balance reservation migration..."); + const migrationStartTime = Date.now(); + + await this.createBalanceReservationTable(); + await this.createUploadAdjustmentTable(); + + await backfillBalanceReservations(this.pg); + + logger.info("Finished balance reservation migration!", { + migrationMs: Date.now() - migrationStartTime, + }); + } + + private async rollbackFromBalanceReservation() { + logger.info("Starting balance reservation rollback..."); + const rollbackStartTime = Date.now(); + + await this.pg.schema.dropTable(balanceReservation); + await this.pg.schema.dropTable(uploadAdjustment); + + logger.info("Finished balance reservation rollback!", { + rollbackMs: Date.now() - rollbackStartTime, + }); + } + + public static migrateToPromoCode(pg: Knex): Promise { + return new Schema(pg).migrateToPromoCode(); + } + + public static rollbackFromPromoCode(pg: Knex): Promise { + return new Schema(pg).rollbackFromPromoCode(); + } + + private async migrateToPromoCode() { + logger.info("Starting promo code migration..."); + const migrationStartTime = Date.now(); + + await this.createPaymentAdjustmentTable(); + + await this.createUploadAdjustmentCatalogTable(); + await addFwdResearchSubsidyUploadCatalogs(this.pg); + + await this.createPaymentAdjustmentCatalogTable(); + + await this.createSingleUseCodePaymentAdjustmentCatalogTable(); + await addToken2049PromoCodeEvent(this.pg); + + // Add quoted payment amount column to top up quote and its extended tables + await this.pg.schema.alterTable(tableNames.topUpQuote, (t) => { + t.string(quotedPaymentAmount).notNullable().defaultTo("0"); + }); + await this.pg.schema.alterTable(tableNames.paymentReceipt, (t) => { + t.string(quotedPaymentAmount).notNullable().defaultTo("0"); + }); + await this.pg.schema.alterTable(tableNames.failedTopUpQuote, (t) => { + t.string(quotedPaymentAmount).notNullable().defaultTo("0"); + }); + await this.pg.schema.alterTable(tableNames.chargebackReceipt, (t) => { + t.string(quotedPaymentAmount).notNullable().defaultTo("0"); + }); + + // Remove adjustment catalog fields from upload adjustment table + await this.pg.schema.alterTable(uploadAdjustment, (t) => { + t.dropColumn(adjustmentDescription); + t.dropColumn(adjustmentName); + t.dropColumn(operator); + t.dropColumn(operatorMagnitude); + + t.string(userAddress) + .notNullable() + .defaultTo("PRE-PROMO-CODE-MIGRATION-USER-ADDRESS") + .index(); + + t.string(catalogId) + .notNullable() + .defaultTo("PRE-PROMO-CODE-MIGRATION-CATALOG-ID") + .index(); + }); + + logger.info("Finished promo code migration!", { + migrationMs: Date.now() - migrationStartTime, + }); + } + + private async rollbackFromPromoCode() { + logger.info("Starting promo code rollback..."); + const rollbackStartTime = Date.now(); + + await this.pg.schema.dropTable(paymentAdjustment); + await this.pg.schema.dropTable(paymentAdjustmentCatalog); + await this.pg.schema.dropTable(uploadAdjustmentCatalog); + await this.pg.schema.dropTable(singleUseCodePaymentAdjustmentCatalog); + + await this.pg.schema.alterTable(topUpQuote, (t) => { + t.dropColumn(quotedPaymentAmount); + }); + await this.pg.schema.alterTable(paymentReceipt, (t) => { + t.dropColumn(quotedPaymentAmount); + }); + await this.pg.schema.alterTable(failedTopUpQuote, (t) => { + t.dropColumn(quotedPaymentAmount); + }); + await this.pg.schema.alterTable(chargebackReceipt, (t) => { + t.dropColumn(quotedPaymentAmount); + }); + + await this.pg.schema.alterTable(uploadAdjustment, (t) => { + // We intend to DROP ALL columns data here and link to CATALOG IDS + // Since its stub data, probably NOT backfilling is fine + t.string(adjustmentDescription).nullable(); + t.string(adjustmentName) + .notNullable() + .defaultTo("FWD Research Upload Subsidy"); + t.string(operator).notNullable().defaultTo("multiply"); + t.decimal(operatorMagnitude).notNullable().defaultTo(0.6); + + t.dropColumn(userAddress); + t.dropColumn(catalogId); + }); + + logger.info("Finished promo code rollback!", { + rollbackMs: Date.now() - rollbackStartTime, + }); + } + + public static migrateForPromoCodeBackfill(pg: Knex): Promise { + return new Schema(pg).migrateForPromoCodeBackfill(); + } + + public static rollbackFromPromoCodeBackfill(pg: Knex): Promise { + return new Schema(pg).rollbackFromPromoCodeBackfill(); + } + + private async migrateForPromoCodeBackfill() { + await backfillUploadAdjustments(this.pg); + + // After backfilling, remove default value from columns + await this.pg.schema.alterTable(uploadAdjustment, (t) => { + t.string(userAddress).notNullable().alter(); + t.string(catalogId).notNullable().alter(); + }); + await this.pg.schema.alterTable(tableNames.topUpQuote, (t) => { + t.string(quotedPaymentAmount).notNullable().alter(); + }); + await this.pg.schema.alterTable(tableNames.paymentReceipt, (t) => { + t.string(quotedPaymentAmount).notNullable().alter(); + }); + await this.pg.schema.alterTable(tableNames.failedTopUpQuote, (t) => { + t.string(quotedPaymentAmount).notNullable().alter(); + }); + + await this.pg.schema.alterTable(tableNames.chargebackReceipt, (t) => { + t.string(quotedPaymentAmount).notNullable().alter(); + }); + } + + private async rollbackFromPromoCodeBackfill() { + logger.info("Starting promo code backfill rollback..."); + const rollbackStartTime = Date.now(); + + // Reapply default values to columns + await this.pg.schema.alterTable(uploadAdjustment, (t) => { + t.string(userAddress) + .notNullable() + .defaultTo("PRE-PROMO-CODE-MIGRATION-USER-ADDRESS") + .alter(); + t.string(catalogId) + .notNullable() + .defaultTo("PRE-PROMO-CODE-MIGRATION-CATALOG-ID") + .alter(); + }); + await this.pg.schema.alterTable(tableNames.topUpQuote, (t) => { + t.string(quotedPaymentAmount).notNullable().defaultTo("0").alter(); + }); + await this.pg.schema.alterTable(tableNames.paymentReceipt, (t) => { + t.string(quotedPaymentAmount).notNullable().defaultTo("0").alter(); + }); + await this.pg.schema.alterTable(tableNames.failedTopUpQuote, (t) => { + t.string(quotedPaymentAmount).notNullable().defaultTo("0").alter(); + }); + + await this.pg.schema.alterTable(tableNames.chargebackReceipt, (t) => { + t.string(quotedPaymentAmount).notNullable().defaultTo("0").alter(); + }); + + logger.info("Finished promo code backfill rollback!", { + rollbackMs: Date.now() - rollbackStartTime, + }); + } + + public static migrateToTurboInfraFee(pg: Knex): Promise { + return new Schema(pg).migrateToTurboInfraFee(); + } + + public static rollbackForTurboInfraFee(pg: Knex): Promise { + return new Schema(pg).rollbackForTurboInfraFee(); + } + + private async migrateToTurboInfraFee() { + logger.info("Starting turbo infra fee migration..."); + const migrationStartTime = Date.now(); + + await addTurboInfraFee(this.pg); + + logger.info("Finished turbo infra fee migration!", { + migrationMs: Date.now() - migrationStartTime, + }); + } + + public static migrateToTargetedPromoCodes(pg: Knex): Promise { + return new Schema(pg).migrateToTargetedPromoCodes(); + } + + public static rollbackFromTargetedPromoCodes(pg: Knex): Promise { + return new Schema(pg).rollbackFromTargetedPromoCodes(); + } + + private async migrateToTargetedPromoCodes() { + logger.info("Starting targeted promo codes migration..."); + const migrationStartTime = Date.now(); + + await this.pg.schema.alterTable( + singleUseCodePaymentAdjustmentCatalog, + (t) => { + t.string("target_user_group").defaultTo("all"); + } + ); + + // Add YOUTUBE promo code with no expiration date as of yet + const youtubeCodeInsert: SingleUseCodePaymentCatalogDBInsert = { + adjustment_name: "Sept 2023 YouTube Promo Code", + adjustment_exclusivity: "exclusive", + catalog_id: randomUUID(), + code_value: "YOUTUBE", + operator: "multiply", + operator_magnitude: "0.8", + adjustment_description: + "20% off top-up purchases, available only to users who have never topped up before.", + target_user_group: "new", + }; + await this.pg( + singleUseCodePaymentAdjustmentCatalog + ).insert(youtubeCodeInsert); + + // Expire TOKEN2049 at end of Sep 29 + await this.pg(singleUseCodePaymentAdjustmentCatalog) + .update({ + adjustment_end_date: "2023-09-30T00:00:00.000Z", + }) + .where({ code_value: "TOKEN2049" }); + + logger.info("Finished targeted promo codes migration!", { + migrationMs: Date.now() - migrationStartTime, + }); + } + + private async rollbackForTurboInfraFee() { + logger.info("Starting turbo infra fee rollback..."); + const rollbackStartTime = Date.now(); + + await this.pg(tableNames.paymentAdjustmentCatalog) + .where({ + [columnNames.adjustmentName]: "Turbo Infrastructure Fee", + }) + .delete(); + + logger.info("Finished turbo infra fee rollback!", { + rollbackMs: Date.now() - rollbackStartTime, + }); + } + private async rollbackFromTargetedPromoCodes() { + logger.info("Starting targeted promo codes rollback..."); + const rollbackStartTime = Date.now(); + + await this.pg.schema.alterTable( + singleUseCodePaymentAdjustmentCatalog, + (t) => { + t.dropColumn("target_user_group"); + } + ); + + // Remove YOUTUBE promo code øn rollback + await this.pg(singleUseCodePaymentAdjustmentCatalog) + .where({ adjustment_name: "Sept 2023 YouTube Promo Code" }) + .delete(); + + // No need to un-expire TOKEN2049 on rollback + + logger.info("Finished targeted promo codes rollback!", { + rollbackMs: Date.now() - rollbackStartTime, + }); + } + + private async createBalanceReservationTable(): Promise { + return this.pg.schema.createTable(balanceReservation, (t) => { + t.string(reservationId).primary(); + t.string(dataItemId).notNullable().index(); + t.string(userAddress).notNullable().index(); + t.string(reservedWincAmount).notNullable(); + t.string(networkWincAmount).notNullable(); + t.timestamp(reservedDate) + .index() + .notNullable() + .defaultTo(this.defaultTimestamp()); + }); + } + + private async createUploadAdjustmentTable(): Promise { + return this.pg.schema.createTable(uploadAdjustment, (t) => { + t.increments(adjustmentId).primary(); + t.string(reservationId).notNullable().index(); + t.string(adjustedWincAmount).notNullable(); + t.integer(adjustmentIndex).notNullable(); + t.timestamp(adjustmentDate) + .index() + .notNullable() + .defaultTo(this.defaultTimestamp()); + + // These were columns removed in PE-3896 + t.string(operator).notNullable(); + t.decimal(operatorMagnitude).notNullable(); + t.string(adjustmentName).notNullable(); + t.string(adjustmentDescription).notNullable(); + }); + } + + private async createPaymentAdjustmentTable(): Promise { + return this.pg.schema.createTable(paymentAdjustment, (t) => { + t.increments(adjustmentId).primary(); + t.string(topUpQuoteId).notNullable().index(); + t.string(userAddress).notNullable().index(); + t.string(adjustedPaymentAmount).notNullable(); + t.string(adjustedCurrencyType).notNullable(); + + t.string(catalogId).notNullable().index(); + t.integer(adjustmentIndex).notNullable(); + t.timestamp(adjustmentDate) + .index() + .notNullable() + .defaultTo(this.defaultTimestamp()); + }); + } + + private catalogTableCreator(t: Knex.CreateTableBuilder): void { + t.string(catalogId).primary(); + t.string(adjustmentName).notNullable(); + t.string(adjustmentDescription).notNullable().defaultTo(""); + + t.integer(adjustmentPriority).notNullable().defaultTo(500); + t.string(operator).notNullable(); + t.string(operatorMagnitude).notNullable(); + + t.timestamp(adjustmentStartDate) + .index() + .notNullable() + .defaultTo(this.defaultTimestamp()); + t.timestamp(adjustmentEndDate).index().nullable(); + } + + private async createUploadAdjustmentCatalogTable(): Promise { + return this.pg.schema.createTable(uploadAdjustmentCatalog, (t) => { + this.catalogTableCreator(t); + }); + } + + private async createPaymentAdjustmentCatalogTable(): Promise { + return this.pg.schema.createTable(paymentAdjustmentCatalog, (t) => { + this.catalogTableCreator(t); + t.string(adjustmentExclusivity).notNullable().defaultTo("inclusive"); + }); + } + + private async createSingleUseCodePaymentAdjustmentCatalogTable(): Promise { + return this.pg.schema.createTable( + singleUseCodePaymentAdjustmentCatalog, + (t) => { + this.catalogTableCreator(t); + t.string(adjustmentExclusivity).notNullable().defaultTo("inclusive"); + t.string(adjustmentCodeValue).notNullable(); + } + ); + } + private async initializeSchema(): Promise { logger.info("Starting initial migration..."); const migrationStartTime = Date.now(); @@ -289,34 +693,62 @@ export class Schema { } const { + uploadAdjustment, auditLog, + balanceReservation, chargebackReceipt, failedTopUpQuote, paymentReceipt, topUpQuote, user, + paymentAdjustment, + paymentAdjustmentCatalog, + singleUseCodePaymentAdjustmentCatalog, + uploadAdjustmentCatalog, } = tableNames; const { + adjustedCurrencyType, + adjustedPaymentAmount, + adjustedWincAmount, + adjustmentCodeValue, + adjustmentDate, + adjustmentDescription, + adjustmentEndDate, + adjustmentExclusivity, + adjustmentName, + adjustmentId, + adjustmentIndex, + adjustmentStartDate, + adjustmentPriority, auditDate, auditId, paymentAmount, + catalogId, changeId, changeReason, chargebackReason, chargebackReceiptDate, chargebackReceiptId, currencyType, + dataItemId, destinationAddress, destinationAddressType, failedReason, + networkWincAmount, + operator, + operatorMagnitude, paymentProvider, paymentReceiptDate, paymentReceiptId, promotionalInfo, quoteCreationDate, + quotedPaymentAmount, quoteExpirationDate, quoteFailedDate, + reservationId, + reservedDate, + reservedWincAmount, topUpQuoteId, userAddress, userAddressType, diff --git a/src/emailProvider.ts b/src/emailProvider.ts new file mode 100644 index 0000000..18c3480 --- /dev/null +++ b/src/emailProvider.ts @@ -0,0 +1,100 @@ +/** + * Copyright (C) 2022-2023 Permanent Data Solutions, Inc. All Rights Reserved. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +import { Mandrill } from "mandrill-api"; +import winston from "winston"; + +import globalLogger from "./logger"; + +interface TemplateContent { + credits: string; + giftCode: string; + recipientEmail: string; + senderEmail?: string; + giftMessage?: string; +} + +export interface EmailProvider { + sendEmail({ + credits, + giftCode, + recipientEmail, + giftMessage, + senderEmail, + }: TemplateContent): Promise; +} + +export class MandrillEmailProvider implements EmailProvider { + private readonly logger; + private readonly mandrillClient: Mandrill; + + constructor(readonly apiKey: string, logger: winston.Logger = globalLogger) { + this.mandrillClient = new Mandrill(apiKey); + this.logger = logger.child({ + class: this.constructor.name, + }); + } + + public async sendEmail({ + credits, + giftCode, + recipientEmail, + giftMessage, + senderEmail, + }: TemplateContent): Promise { + const templateName = "gift-credits"; + const templateContent = [ + { + name: "CREDITS", + content: credits, + }, + { + name: "CODE", + content: giftCode, + }, + { + name: "GIFTMESSAGE", + content: giftMessage, + }, + { + name: "SENDEREMAIL", + content: senderEmail, + }, + ]; + + try { + const result = await this.mandrillClient.messages.sendTemplate({ + async: true, + template_name: templateName, + template_content: [], + message: { + to: [ + { + email: recipientEmail, + name: recipientEmail, + type: "to", + }, + ], + global_merge_vars: templateContent, + }, + }); + this.logger.info("Email sent successfully", result); + } catch (error) { + this.logger.error("Failed to send email via Mandrill!", error); + throw error; + } + } +} diff --git a/src/metricRegistry.ts b/src/metricRegistry.ts index ac7f9e7..737c4d6 100644 --- a/src/metricRegistry.ts +++ b/src/metricRegistry.ts @@ -54,11 +54,24 @@ export class MetricRegistry { help: "Count of uncaught exceptions", }); + // TODO: add metric that tracks fraudulent wallet addresses public static suspiciousWalletActivity = new promClient.Counter({ name: "suspicious_wallet_activity", help: "Count of suspicious wallet activity (e.g. high number of chargebacks)", }); + // Protected routes are obscured from the public, but still require authorization + // If we see activity on these routes, it's likely a bad actor inside our infrastructure + public static unauthorizedProtectedRouteActivity = new promClient.Counter({ + name: "unauthorized_protected_route_activity", + help: "Count of unauthorized activity on protected routes", + }); + + public static giftEmailTriggerFailure = new promClient.Counter({ + name: "gift_email_trigger_failure", + help: "Count of gift email trigger failures for unredeemed gifts", + }); + private constructor() { this.registry = new promClient.Registry(); @@ -72,6 +85,10 @@ export class MetricRegistry { MetricRegistry.stripeSessionCreationErrorCounter ); this.registry.registerMetric(MetricRegistry.suspiciousWalletActivity); + this.registry.registerMetric( + MetricRegistry.unauthorizedProtectedRouteActivity + ); + this.registry.registerMetric(MetricRegistry.giftEmailTriggerFailure); } public static getInstance(): MetricRegistry { diff --git a/src/middleware/architecture.ts b/src/middleware/architecture.ts index 6ac3a64..fd270e9 100644 --- a/src/middleware/architecture.ts +++ b/src/middleware/architecture.ts @@ -27,5 +27,6 @@ export async function architectureMiddleware( ctx.state.paymentDatabase = arch.paymentDatabase; ctx.state.pricingService = arch.pricingService; ctx.state.stripe = arch.stripe; + ctx.state.emailProvider = arch.emailProvider; return next(); } diff --git a/src/middleware/logger.ts b/src/middleware/logger.ts index a5065a5..0943cde 100644 --- a/src/middleware/logger.ts +++ b/src/middleware/logger.ts @@ -27,7 +27,10 @@ export async function loggerMiddleware(ctx: KoaContext, next: Next) { path: ctx.path, method: ctx.method, params: ctx.params, + query: ctx.query, }); + // TODO: replace with opentelemetry middleware, log the request headers once to track SDK usage. + log.info("Request headers", { headers: ctx.headers }); ctx.state.logger = log; ctx.state.trace = trace; const startTime = Date.now(); diff --git a/src/middleware/verifySignature.ts b/src/middleware/verifySignature.ts index a8a6713..771c60c 100644 --- a/src/middleware/verifySignature.ts +++ b/src/middleware/verifySignature.ts @@ -42,12 +42,12 @@ export async function verifySignature(ctx: Context, next: Next): Promise { } logger.info("Verifying arweave signature"); + // TODO: use a factory that verifies, validates and returns address of provided x-public-key-header const isVerified = await verifyArweaveSignature({ publicKey, signature: fromB64UrlToBuffer(signature), - additionalData: Object.keys(ctx.request.query).length - ? JSON.stringify(ctx.request.query) - : undefined, + // TODO: Verify from additional DATA on POST + additionalData: undefined, nonce: nonce, }); @@ -55,7 +55,7 @@ export async function verifySignature(ctx: Context, next: Next): Promise { if (isVerified) { // Attach wallet address for the next middleware - ctx.state.walletAddress = await arweaveRSAModulusToAddress(publicKey); + ctx.state.walletAddress = arweaveRSAModulusToAddress(publicKey); // Generate a JWT token for subsequent requests logger.info("Generating JWT token for wallet.", { wallet: ctx.state.walletAddress, diff --git a/src/migrations/20230316184829_initial.ts b/src/migrations/20230316184829_initial.ts new file mode 100644 index 0000000..b3982a5 --- /dev/null +++ b/src/migrations/20230316184829_initial.ts @@ -0,0 +1,27 @@ +/** + * Copyright (C) 2022-2023 Permanent Data Solutions, Inc. All Rights Reserved. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +import { Knex } from "knex"; + +import { Schema } from "../database/schema"; + +export async function up(knex: Knex): Promise { + return Schema.create(knex); +} + +export async function down(knex: Knex): Promise { + return Schema.rollback(knex); +} diff --git a/src/migrations/20230714170814_audit_log.ts b/src/migrations/20230714170814_audit_log.ts new file mode 100644 index 0000000..35b9df2 --- /dev/null +++ b/src/migrations/20230714170814_audit_log.ts @@ -0,0 +1,27 @@ +/** + * Copyright (C) 2022-2023 Permanent Data Solutions, Inc. All Rights Reserved. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +import { Knex } from "knex"; + +import { Schema } from "../database/schema"; + +export async function up(knex: Knex): Promise { + return Schema.migrateToAuditLog(knex); +} + +export async function down(knex: Knex): Promise { + return Schema.rollbackFromAuditLog(knex); +} diff --git a/src/migrations/20230717155202_audit_log_credit_pos_neg.ts b/src/migrations/20230717155202_audit_log_credit_pos_neg.ts new file mode 100644 index 0000000..33afc4c --- /dev/null +++ b/src/migrations/20230717155202_audit_log_credit_pos_neg.ts @@ -0,0 +1,27 @@ +/** + * Copyright (C) 2022-2023 Permanent Data Solutions, Inc. All Rights Reserved. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +import { Knex } from "knex"; + +import { Schema } from "../database/schema"; + +export async function up(knex: Knex): Promise { + return Schema.migrateAuditLogToPositiveNegativeCredits(knex); +} + +export async function down(knex: Knex): Promise { + return Schema.rollBackFromMigrateAuditLogToPositiveNegativeCredits(knex); +} diff --git a/src/migrations/20230726205440_balance_reservation.ts b/src/migrations/20230726205440_balance_reservation.ts new file mode 100644 index 0000000..a9cd9f3 --- /dev/null +++ b/src/migrations/20230726205440_balance_reservation.ts @@ -0,0 +1,27 @@ +/** + * Copyright (C) 2022-2023 Permanent Data Solutions, Inc. All Rights Reserved. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +import { Knex } from "knex"; + +import { Schema } from "../database/schema"; + +export async function up(knex: Knex): Promise { + return Schema.migrateToBalanceReservation(knex); +} + +export async function down(knex: Knex): Promise { + return Schema.rollbackFromBalanceReservation(knex); +} diff --git a/src/migrations/20230823174144_promo_codes.ts b/src/migrations/20230823174144_promo_codes.ts new file mode 100644 index 0000000..51571dd --- /dev/null +++ b/src/migrations/20230823174144_promo_codes.ts @@ -0,0 +1,27 @@ +/** + * Copyright (C) 2022-2023 Permanent Data Solutions, Inc. All Rights Reserved. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +import { Knex } from "knex"; + +import { Schema } from "../database/schema"; + +export async function up(knex: Knex): Promise { + return Schema.migrateToPromoCode(knex); +} + +export async function down(knex: Knex): Promise { + return Schema.rollbackFromPromoCode(knex); +} diff --git a/src/migrations/20230831194447_promo_code_backfill.ts b/src/migrations/20230831194447_promo_code_backfill.ts new file mode 100644 index 0000000..b04caaa --- /dev/null +++ b/src/migrations/20230831194447_promo_code_backfill.ts @@ -0,0 +1,27 @@ +/** + * Copyright (C) 2022-2023 Permanent Data Solutions, Inc. All Rights Reserved. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +import { Knex } from "knex"; + +import { Schema } from "../database/schema"; + +export async function up(knex: Knex): Promise { + return Schema.migrateForPromoCodeBackfill(knex); +} + +export async function down(knex: Knex): Promise { + return Schema.rollbackFromPromoCodeBackfill(knex); +} diff --git a/src/migrations/20230922191015_add_turbo_infra_fee.ts b/src/migrations/20230922191015_add_turbo_infra_fee.ts new file mode 100644 index 0000000..df838f8 --- /dev/null +++ b/src/migrations/20230922191015_add_turbo_infra_fee.ts @@ -0,0 +1,27 @@ +/** + * Copyright (C) 2022-2023 Permanent Data Solutions, Inc. All Rights Reserved. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +import { Knex } from "knex"; + +import { Schema } from "../database/schema"; + +export async function up(knex: Knex): Promise { + return Schema.migrateToTurboInfraFee(knex); +} + +export async function down(knex: Knex): Promise { + return Schema.rollbackForTurboInfraFee(knex); +} diff --git a/src/migrations/20230926164504_targeted_promo_codes.ts b/src/migrations/20230926164504_targeted_promo_codes.ts new file mode 100644 index 0000000..93b4df7 --- /dev/null +++ b/src/migrations/20230926164504_targeted_promo_codes.ts @@ -0,0 +1,27 @@ +/** + * Copyright (C) 2022-2023 Permanent Data Solutions, Inc. All Rights Reserved. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +import { Knex } from "knex"; + +import { Schema } from "../database/schema"; + +export async function up(knex: Knex): Promise { + return Schema.migrateToTargetedPromoCodes(knex); +} + +export async function down(knex: Knex): Promise { + return Schema.rollbackFromTargetedPromoCodes(knex); +} diff --git a/src/migrations/20230927141634_backfill_infra_fee.ts b/src/migrations/20230927141634_backfill_infra_fee.ts new file mode 100644 index 0000000..cc36c1d --- /dev/null +++ b/src/migrations/20230927141634_backfill_infra_fee.ts @@ -0,0 +1,27 @@ +/** + * Copyright (C) 2022-2023 Permanent Data Solutions, Inc. All Rights Reserved. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +import { Knex } from "knex"; + +import { BackfillInfraFeeMigrator } from "../database/migrator"; + +export async function up(knex: Knex): Promise { + return new BackfillInfraFeeMigrator(knex).migrate(); +} + +export async function down(knex: Knex): Promise { + return new BackfillInfraFeeMigrator(knex).rollback(); +} diff --git a/src/migrations/20230928161352_pilot_referral.ts b/src/migrations/20230928161352_pilot_referral.ts new file mode 100644 index 0000000..60d16fc --- /dev/null +++ b/src/migrations/20230928161352_pilot_referral.ts @@ -0,0 +1,27 @@ +/** + * Copyright (C) 2022-2023 Permanent Data Solutions, Inc. All Rights Reserved. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +import { Knex } from "knex"; + +import { PilotReferralMigrator } from "../database/migrator"; + +export async function up(knex: Knex): Promise { + return new PilotReferralMigrator(knex).migrate(); +} + +export async function down(knex: Knex): Promise { + return new PilotReferralMigrator(knex).rollback(); +} diff --git a/src/migrations/20231030154946_max_discount.ts b/src/migrations/20231030154946_max_discount.ts new file mode 100644 index 0000000..9fc14a7 --- /dev/null +++ b/src/migrations/20231030154946_max_discount.ts @@ -0,0 +1,27 @@ +/** + * Copyright (C) 2022-2023 Permanent Data Solutions, Inc. All Rights Reserved. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +import { Knex } from "knex"; + +import { MaxDiscountMigrator } from "../database/migrator"; + +export async function up(knex: Knex): Promise { + return new MaxDiscountMigrator(knex).migrate(); +} + +export async function down(knex: Knex): Promise { + return new MaxDiscountMigrator(knex).rollback(); +} diff --git a/src/migrations/20231129173451_gift_by_email.ts b/src/migrations/20231129173451_gift_by_email.ts new file mode 100644 index 0000000..6159d03 --- /dev/null +++ b/src/migrations/20231129173451_gift_by_email.ts @@ -0,0 +1,27 @@ +/** + * Copyright (C) 2022-2023 Permanent Data Solutions, Inc. All Rights Reserved. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +import { Knex } from "knex"; + +import { GiftByEmailMigrator } from "../database/migrator"; + +export async function up(knex: Knex): Promise { + return new GiftByEmailMigrator(knex).migrate(); +} + +export async function down(knex: Knex): Promise { + return new GiftByEmailMigrator(knex).rollback(); +} diff --git a/src/pricing/oracles/arweaveToFiatOracle.ts b/src/pricing/oracles/arweaveToFiatOracle.ts index 745ffe5..6f147cf 100644 --- a/src/pricing/oracles/arweaveToFiatOracle.ts +++ b/src/pricing/oracles/arweaveToFiatOracle.ts @@ -14,9 +14,12 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ +import { + CacheParams, + ReadThroughPromiseCache, +} from "@ardrive/ardrive-promise-cache"; + import { createAxiosInstance } from "../../axiosClient"; -import { CacheParams } from "../../cache/promiseCache"; -import { ReadThroughPromiseCache } from "../../cache/readThroughPromiseCache"; import logger from "../../logger"; import { supportedPaymentCurrencyTypes } from "../../types/supportedCurrencies"; @@ -74,6 +77,8 @@ export class CoingeckoArweaveToFiatOracle implements ArweaveToFiatOracle { } } +const oneMinuteMs = 60 * 1000; + export class ReadThroughArweaveToFiatOracle { private readonly oracle: ArweaveToFiatOracle; private readonly readThroughPromiseCache: ReadThroughPromiseCache< @@ -90,8 +95,13 @@ export class ReadThroughArweaveToFiatOracle { }) { this.oracle = oracle ?? new CoingeckoArweaveToFiatOracle(); this.readThroughPromiseCache = new ReadThroughPromiseCache({ - cacheParams: cacheParams ?? { cacheCapacity: 10 }, - readThroughFunction: () => this.oracle.getFiatPricesForOneAR(), + cacheParams: cacheParams ?? { + cacheCapacity: 10, + cacheTTL: oneMinuteMs, + }, + readThroughFunction: () => + // TODO: Get from service level cache before oracle (elasticache) + this.oracle.getFiatPricesForOneAR(), }); } diff --git a/src/pricing/oracles/bytesToWinstonOracle.ts b/src/pricing/oracles/bytesToWinstonOracle.ts index 8214748..ad1a20d 100644 --- a/src/pricing/oracles/bytesToWinstonOracle.ts +++ b/src/pricing/oracles/bytesToWinstonOracle.ts @@ -14,6 +14,10 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ +import { + CacheParams, + ReadThroughPromiseCache, +} from "@ardrive/ardrive-promise-cache"; import { AxiosInstance } from "axios"; import BigNumber from "bignumber.js"; @@ -21,8 +25,6 @@ import { CreateAxiosInstanceParams, createAxiosInstance, } from "../../axiosClient"; -import { CacheParams } from "../../cache/promiseCache"; -import { ReadThroughPromiseCache } from "../../cache/readThroughPromiseCache"; import { msPerMinute } from "../../constants"; import logger from "../../logger"; import { ByteCount, Winston } from "../../types"; @@ -63,6 +65,7 @@ export class ReadThroughBytesToWinstonOracle { Winston >; private getWinstonForBytesFromOracle = async (bytes: ByteCount) => { + //TODO Get from elasticache first return this.oracle.getWinstonForBytes(bytes); }; diff --git a/src/pricing/price.ts b/src/pricing/price.ts new file mode 100644 index 0000000..c74697a --- /dev/null +++ b/src/pricing/price.ts @@ -0,0 +1,74 @@ +/** + * Copyright (C) 2022-2023 Permanent Data Solutions, Inc. All Rights Reserved. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +import { WC } from "../types"; + +abstract class Price { + constructor(public readonly winc: WC) {} + + toString(): string { + return this.winc.toString(); + } + valueOf(): string { + return this.winc.valueOf(); + } + toJSON(): string { + return this.winc.toJSON(); + } +} + +export class NetworkPrice extends Price { + constructor(public readonly winc: WC) { + super(winc); + } +} + +export class SubtotalPrice extends Price { + constructor(public readonly winc: WC) { + super(winc); + } + + static addFromNetworkPrice( + networkPrice: NetworkPrice, + winc: WC + ): SubtotalPrice { + return new SubtotalPrice(networkPrice.winc).add(winc); + } + + static multiplyFromNetworkPrice( + networkPrice: NetworkPrice, + multiple: number + ): SubtotalPrice { + return new SubtotalPrice(networkPrice.winc).multiply(multiple); + } + + public add(winc: WC): SubtotalPrice { + return new SubtotalPrice(this.winc.plus(winc)); + } + public multiply(multiple: number): SubtotalPrice { + return new SubtotalPrice(this.winc.times(multiple)); + } +} + +export class FinalPrice extends Price { + constructor(public readonly winc: WC) { + super(winc); + } + + static fromSubtotal(subtotal: SubtotalPrice) { + return new FinalPrice(subtotal.winc); + } +} diff --git a/src/pricing/pricing.test.ts b/src/pricing/pricing.test.ts new file mode 100644 index 0000000..047d33a --- /dev/null +++ b/src/pricing/pricing.test.ts @@ -0,0 +1,687 @@ +/** + * Copyright (C) 2022-2023 Permanent Data Solutions, Inc. All Rights Reserved. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +import { expect } from "chai"; +import { SinonFakeTimers, stub, useFakeTimers } from "sinon"; + +import { expectedArPrices } from "../../tests/helpers/stubs"; +import { + expectAsyncErrorThrow, + removeCatalogIdMap, +} from "../../tests/helpers/testHelpers"; +import { tableNames } from "../database/dbConstants"; +import { + SingleUseCodePaymentCatalogDBInsert, + SingleUseCodePaymentCatalogDBResult, + UploadAdjustmentCatalogDBInsert, +} from "../database/dbTypes"; +import { PostgresDatabase } from "../database/postgres"; +import { ByteCount, Winston } from "../types"; +import { Payment } from "../types/payment"; +import { roundToArweaveChunkSize } from "../utils/roundToChunkSize"; +import { + CoingeckoArweaveToFiatOracle, + ReadThroughArweaveToFiatOracle, +} from "./oracles/arweaveToFiatOracle"; +import { ReadThroughBytesToWinstonOracle } from "./oracles/bytesToWinstonOracle"; +import { TurboPricingService } from "./pricing"; + +describe("TurboPricingService class", () => { + const paymentDatabase = new PostgresDatabase(); + const bytesToWinstonOracle = new ReadThroughBytesToWinstonOracle({}); + const oracle = new CoingeckoArweaveToFiatOracle(); + const arweaveToFiatOracle = new ReadThroughArweaveToFiatOracle({ oracle }); + + const pricing = new TurboPricingService({ + arweaveToFiatOracle, + bytesToWinstonOracle, + paymentDatabase, + }); + + describe("getWCForBytes", () => { + describe("with a date before the subsidy", () => { + let clock: SinonFakeTimers; + before(() => { + const fakeDateBeforeSubsidy = new Date("2021-01-01T00:00:00.000Z"); + clock = useFakeTimers(fakeDateBeforeSubsidy.getTime()); + }); + after(() => { + clock.restore(); + }); + + beforeEach(() => { + stub(bytesToWinstonOracle, "getWinstonForBytes").callsFake((b) => + // Return the given byte count (rounded as chunks) as the stubbed price + Promise.resolve(new Winston(b.toString())) + ); + }); + + it("returns the expected price for a given byte count", async () => { + const price = await pricing.getWCForBytes(ByteCount(100)); + expect(price.finalPrice.winc.toString()).to.equal( + `${roundToArweaveChunkSize(ByteCount(100))}` + ); + }); + + describe("when a flat discount upload promo event is applied...", () => { + const startDate = new Date("2001-01-02T00:00:00.000Z"); + const endDate = new Date("2001-01-04T00:00:00.000Z"); + + const dateBeforeDiscount = new Date("2001-01-01T00:00:00.000Z"); + const dateDuringDiscount = new Date("2001-01-03T00:00:00.000Z"); + const dateAfterDiscount = new Date("2001-01-05T00:00:00.000Z"); + + before(async () => { + const insert: UploadAdjustmentCatalogDBInsert = { + adjustment_name: "Turbo 1 million winc off", + catalog_id: "best_stub_id_ever", + operator: "add", + adjustment_end_date: endDate.toISOString(), + operator_magnitude: "-1000000", + adjustment_start_date: startDate.toISOString(), + }; + + await paymentDatabase["writer"]( + tableNames.uploadAdjustmentCatalog + ).insert(insert); + }); + + after(async () => { + await paymentDatabase["writer"](tableNames.uploadAdjustmentCatalog) + .where({ catalog_id: "best_stub_id_ever_2" }) + .delete(); + }); + + afterEach(() => { + clock.restore(); + }); + + it("returns the expected price for a given byte count larger than the discount", async () => { + clock = useFakeTimers(dateDuringDiscount.getTime()); + + const { adjustments, finalPrice, networkPrice } = + await pricing.getWCForBytes(ByteCount(1_048_576)); + + expect(finalPrice.winc.toString()).to.equal("48576"); + expect(networkPrice.winc.toString()).to.equal("1048576"); + + expect(adjustments).to.deep.equal([ + { + adjustmentAmount: new Winston(-1000000), + description: "", + name: "Turbo 1 million winc off", + operator: "add", + operatorMagnitude: -1000000, + catalogId: "best_stub_id_ever", + }, + ]); + }); + + it("returns the expected price for a given byte count smaller than the discount", async () => { + clock = useFakeTimers(dateDuringDiscount.getTime()); + + const { adjustments, finalPrice, networkPrice } = + await pricing.getWCForBytes(ByteCount(256 * 1024)); + + expect(finalPrice.winc.toString()).to.equal("0"); + expect(networkPrice.winc.toString()).to.equal("262144"); + + expect(adjustments).to.deep.equal([ + { + adjustmentAmount: new Winston(-262144), + description: "", + name: "Turbo 1 million winc off", + operator: "add", + operatorMagnitude: -1000000, + catalogId: "best_stub_id_ever", + }, + ]); + }); + + it("returns the expected price for a given byte count when the discount is expired", async () => { + clock = useFakeTimers(dateAfterDiscount.getTime()); + + const { adjustments, finalPrice, networkPrice } = + await pricing.getWCForBytes(ByteCount(256 * 1024)); + + expect(finalPrice.winc.toString()).to.equal("262144"); + expect(networkPrice.winc.toString()).to.equal("262144"); + + expect(adjustments).to.deep.equal([]); + }); + + it("returns the expected price for a given byte before the discount has started", async () => { + clock = useFakeTimers(dateBeforeDiscount.getTime()); + + const { adjustments, finalPrice, networkPrice } = + await pricing.getWCForBytes(ByteCount(256 * 1024)); + + expect(finalPrice.winc.toString()).to.equal("262144"); + expect(networkPrice.winc.toString()).to.equal("262144"); + + expect(adjustments).to.deep.equal([]); + }); + }); + }); + + describe("with dates throughout the subsidy", () => { + beforeEach(() => { + stub(bytesToWinstonOracle, "getWinstonForBytes").resolves( + new Winston(100) + ); + }); + + const datesAndExpectedPrices = [ + ["2023-07-14", "100"], + ["2023-07-15", "40"], + ["2023-08-14", "40"], + ["2023-08-15", "47"], + ["2023-09-14", "47"], + ["2023-09-15", "55"], + ["2023-10-14", "55"], + ["2023-10-15", "62"], + ["2023-11-14", "62"], + ["2023-11-15", "70"], + ["2023-12-14", "70"], + ["2023-12-15", "77"], + ["2024-01-14", "77"], + ["2024-01-15", "85"], + ["2024-02-14", "85"], + ["2024-02-15", "92"], + ["2024-03-14", "92"], + ["2024-03-15", "100"], + ]; + + for (const [date, expectedPrice] of datesAndExpectedPrices) { + it(`returns the expected price (${expectedPrice}) for date "${date}" of the fwd research promotion`, async () => { + const fakeDate = new Date(`${date}T00:00:00.000Z`); + const clock = useFakeTimers(fakeDate.getTime()); + + const price = await pricing.getWCForBytes(ByteCount(100)); + expect(price.finalPrice.winc.toString()).to.equal(expectedPrice); + + clock.restore(); + }); + } + }); + }); + + describe("getWCForPayment", () => { + beforeEach(() => { + stub(oracle, "getFiatPricesForOneAR").resolves(expectedArPrices.arweave); + }); + + it("returns the expected price for a given payment", async () => { + const { + actualPaymentAmount, + adjustments, + inclusiveAdjustments, + finalPrice, + quotedPaymentAmount, + } = await pricing.getWCForPayment({ + payment: new Payment({ amount: 100, type: "usd" }), + promoCodes: [], + }); + + expect(finalPrice.winc.toString()).to.equal("109686609687"); + expect(actualPaymentAmount).to.equal(100); + expect(quotedPaymentAmount).to.equal(100); + expect(adjustments).to.deep.equal([]); + expect(inclusiveAdjustments.map(removeCatalogIdMap)).to.deep.equal([ + { + adjustmentAmount: -23, + currencyType: "usd", + description: + "Inclusive usage fee on all payments to cover infrastructure costs and payment provider fees.", + name: "Turbo Infrastructure Fee", + operator: "multiply", + operatorMagnitude: 0.766, + }, + ]); + }); + + it("returns the expected price for a given payment with a 20% promo event applied", async () => { + const pricingTestPromoCode = "pricingTestPromoCode"; + const pricingTestPromoCodeCatalogId = "pricingTestPromoCodeCatalogId"; + + await paymentDatabase["writer"]( + tableNames.singleUseCodePaymentAdjustmentCatalog + ).insert({ + code_value: pricingTestPromoCode, + adjustment_exclusivity: "exclusive", + adjustment_name: "Pricing Test Promo Code", + catalog_id: pricingTestPromoCodeCatalogId, + operator: "multiply", + operator_magnitude: "0.8", + adjustment_start_date: "2021-01-01T00:00:00.000Z", // some time in the past + }); + + const price = await pricing.getWCForPayment({ + payment: new Payment({ amount: 100, type: "usd" }), + promoCodes: [pricingTestPromoCode], + userAddress: "StubUniqueUserAddress", + }); + expect(price.finalPrice.winc.toString()).to.equal("109686609687"); + expect(price.actualPaymentAmount).to.equal(80); + expect(price.quotedPaymentAmount).to.equal(100); + expect(price.adjustments.map(removeCatalogIdMap)).to.deep.equal([ + { + adjustmentAmount: -20, + currencyType: "usd", + description: "", + name: "Pricing Test Promo Code", + operator: "multiply", + operatorMagnitude: 0.8, + promoCode: "pricingTestPromoCode", + }, + ]); + expect(price.inclusiveAdjustments.map(removeCatalogIdMap)).to.deep.equal([ + { + adjustmentAmount: -23, + currencyType: "usd", + description: + "Inclusive usage fee on all payments to cover infrastructure costs and payment provider fees.", + name: "Turbo Infrastructure Fee", + operator: "multiply", + operatorMagnitude: 0.766, + }, + ]); + expect(price.adjustments[0].catalogId).to.be.a("string"); + }); + + it("returns the expected price for a zero amount", async () => { + const price = await pricing.getWCForPayment({ + payment: new Payment({ amount: 0, type: "usd" }), + }); + expect(price.finalPrice.winc.toString()).to.equal("0"); + expect(price.actualPaymentAmount).to.equal(0); + expect(price.quotedPaymentAmount).to.equal(0); + expect(price.adjustments).to.deep.equal([]); + expect(price.inclusiveAdjustments.map(removeCatalogIdMap)).to.deep.equal([ + { + adjustmentAmount: 0, + currencyType: "usd", + description: + "Inclusive usage fee on all payments to cover infrastructure costs and payment provider fees.", + name: "Turbo Infrastructure Fee", + operator: "multiply", + operatorMagnitude: 0.766, + }, + ]); + }); + + it("returns the expected price for one us cent", async () => { + const price = await pricing.getWCForPayment({ + payment: new Payment({ amount: 1, type: "usd" }), + }); + expect(price.finalPrice.winc.toString()).to.equal("1424501425"); + expect(price.actualPaymentAmount).to.equal(1); + expect(price.quotedPaymentAmount).to.equal(1); + expect(price.adjustments).to.deep.equal([]); + expect(price.inclusiveAdjustments.map(removeCatalogIdMap)).to.deep.equal([ + { + adjustmentAmount: 0, + currencyType: "usd", + description: + "Inclusive usage fee on all payments to cover infrastructure costs and payment provider fees.", + name: "Turbo Infrastructure Fee", + operator: "multiply", + operatorMagnitude: 0.766, + }, + ]); + }); + + it("returns the expected price for forty nine us cents", async () => { + const price = await pricing.getWCForPayment({ + payment: new Payment({ amount: 49, type: "usd" }), + }); + expect(price.finalPrice.winc.toString()).to.equal("54131054131"); + expect(price.actualPaymentAmount).to.equal(49); + expect(price.quotedPaymentAmount).to.equal(49); + expect(price.adjustments).to.deep.equal([]); + expect(price.inclusiveAdjustments.map(removeCatalogIdMap)).to.deep.equal([ + { + adjustmentAmount: -11, + currencyType: "usd", + description: + "Inclusive usage fee on all payments to cover infrastructure costs and payment provider fees.", + name: "Turbo Infrastructure Fee", + operator: "multiply", + operatorMagnitude: 0.766, + }, + ]); + }); + + describe("when a flat discount promo event is applied...", () => { + before(async () => { + const insert: SingleUseCodePaymentCatalogDBInsert = { + adjustment_name: "Turbo One Dollar Off", + catalog_id: "best_stub_id", + code_value: "TURBO_ONE_DOLLAR_OFF", + operator: "add", + operator_magnitude: "-100", + adjustment_exclusivity: "exclusive", + adjustment_start_date: new Date( + "2021-01-01T00:00:00.000Z" + ).toISOString(), + }; + + await paymentDatabase["writer"]( + tableNames.singleUseCodePaymentAdjustmentCatalog + ).insert(insert); + }); + + it("returns the expected price for a given payment", async () => { + const price = await pricing.getWCForPayment({ + payment: new Payment({ amount: 200, type: "usd" }), + promoCodes: ["TURBO_ONE_DOLLAR_OFF"], + userAddress: "StubUniqueUserAddress", + }); + expect(price.finalPrice.winc.toString()).to.equal("217948717949"); + expect(price.actualPaymentAmount).to.equal(100); + expect(price.quotedPaymentAmount).to.equal(200); + expect(price.adjustments.map(removeCatalogIdMap)).to.deep.equal([ + { + adjustmentAmount: -100, + currencyType: "usd", + description: "", + name: "Turbo One Dollar Off", + operator: "add", + operatorMagnitude: -100, + promoCode: "TURBO_ONE_DOLLAR_OFF", + }, + ]); + expect( + price.inclusiveAdjustments.map(removeCatalogIdMap) + ).to.deep.equal([ + { + adjustmentAmount: -47, + currencyType: "usd", + description: + "Inclusive usage fee on all payments to cover infrastructure costs and payment provider fees.", + name: "Turbo Infrastructure Fee", + operator: "multiply", + operatorMagnitude: 0.766, + }, + ]); + }); + + it("returns the expected minimum price for a given payment when a discount would go below the minimum", async () => { + const price = await pricing.getWCForPayment({ + payment: new Payment({ amount: 80, type: "usd" }), + promoCodes: ["TURBO_ONE_DOLLAR_OFF"], + userAddress: "StubUniqueUserAddress", + }); + expect(price.finalPrice.winc.toString()).to.equal("86894586895"); + expect(price.actualPaymentAmount).to.equal(0); + expect(price.quotedPaymentAmount).to.equal(80); + expect(price.adjustments.map(removeCatalogIdMap)).to.deep.equal([ + { + adjustmentAmount: -80, + currencyType: "usd", + description: "", + name: "Turbo One Dollar Off", + operator: "add", + operatorMagnitude: -100, + promoCode: "TURBO_ONE_DOLLAR_OFF", + }, + ]); + expect( + price.inclusiveAdjustments.map(removeCatalogIdMap) + ).to.deep.equal([ + { + adjustmentAmount: -19, + currencyType: "usd", + description: + "Inclusive usage fee on all payments to cover infrastructure costs and payment provider fees.", + name: "Turbo Infrastructure Fee", + operator: "multiply", + operatorMagnitude: 0.766, + }, + ]); + }); + + it("returns the expected response for a given payment when a discount is applied in JPY", async () => { + const price = await pricing.getWCForPayment({ + payment: new Payment({ amount: 10000, type: "jpy" }), + promoCodes: ["TURBO_ONE_DOLLAR_OFF"], + userAddress: "StubUniqueUserAddress", + }); + expect(price.finalPrice.winc.toString()).to.equal("8120256116694"); + expect(price.actualPaymentAmount).to.equal(9866); + expect(price.quotedPaymentAmount).to.equal(10000); + expect(price.adjustments.map(removeCatalogIdMap)).to.deep.equal([ + { + adjustmentAmount: -134, + currencyType: "jpy", + description: "", + name: "Turbo One Dollar Off", + operator: "add", + operatorMagnitude: -100, + promoCode: "TURBO_ONE_DOLLAR_OFF", + }, + ]); + expect( + price.inclusiveAdjustments.map(removeCatalogIdMap) + ).to.deep.equal([ + { + adjustmentAmount: -2340, + currencyType: "jpy", + description: + "Inclusive usage fee on all payments to cover infrastructure costs and payment provider fees.", + name: "Turbo Infrastructure Fee", + operator: "multiply", + operatorMagnitude: 0.766, + }, + ]); + }); + }); + + describe("with a pilot referral event added", () => { + const pricingPilotReferralPromoCode = "pricingPilotReferralPromoCode"; + const pricingPilotReferralPromoCodeCatalogId = + "pricingPilotReferralPromoCodeCatalogId"; + + before(async () => { + await paymentDatabase["writer"]( + tableNames.singleUseCodePaymentAdjustmentCatalog + ).insert({ + code_value: pricingPilotReferralPromoCode, + adjustment_exclusivity: "exclusive", + adjustment_name: "Pricing Pilot Referral Promo Code", + catalog_id: pricingPilotReferralPromoCodeCatalogId, + target_user_group: "new", + max_uses: 10, + minimum_payment_amount: 1000, + operator: "add", + operator_magnitude: "-500", + adjustment_start_date: "2023-09-20T16:47:37.660Z", // in the past + }); + }); + + it("returns the expected adjustment when within minimum payment amount", async () => { + const userAddress = "userAddress"; + + const { + actualPaymentAmount, + adjustments, + finalPrice, + inclusiveAdjustments, + quotedPaymentAmount, + } = await pricing.getWCForPayment({ + payment: new Payment({ amount: 1000, type: "usd" }), + promoCodes: [pricingPilotReferralPromoCode], + userAddress, + }); + + expect(finalPrice.winc.toString()).to.equal("1091168091168"); + expect(actualPaymentAmount).to.equal(500); + expect(quotedPaymentAmount).to.equal(1000); + expect(adjustments.map(removeCatalogIdMap)).to.deep.equal([ + { + adjustmentAmount: -500, + currencyType: "usd", + description: "", + name: "Pricing Pilot Referral Promo Code", + operator: "add", + operatorMagnitude: -500, + promoCode: "pricingPilotReferralPromoCode", + }, + ]); + expect(inclusiveAdjustments.map(removeCatalogIdMap)).to.deep.equal([ + { + adjustmentAmount: -234, + currencyType: "usd", + description: + "Inclusive usage fee on all payments to cover infrastructure costs and payment provider fees.", + name: "Turbo Infrastructure Fee", + operator: "multiply", + operatorMagnitude: 0.766, + }, + ]); + }); + + it("errors as expected when used below minimum payment amount", async () => { + await expectAsyncErrorThrow({ + promiseToError: pricing.getWCForPayment({ + payment: new Payment({ amount: 999, type: "usd" }), + promoCodes: [pricingPilotReferralPromoCode], + userAddress: "userAddress", + }), + errorMessage: + "The promo code 'pricingPilotReferralPromoCode' can only used on payments above '1000'", + errorType: "PaymentAmountTooSmallForPromoCode", + }); + }); + + it("errors as expected when used below minimum payment amount in JPY", async () => { + await expectAsyncErrorThrow({ + promiseToError: pricing.getWCForPayment({ + payment: new Payment({ amount: 500, type: "jpy" }), + promoCodes: [pricingPilotReferralPromoCode], + userAddress: "userAddress", + }), + errorMessage: + "The promo code 'pricingPilotReferralPromoCode' can only used on payments above '1000'", + errorType: "PaymentAmountTooSmallForPromoCode", + }); + }); + }); + + describe("with an event with max discount is added", () => { + const pricingMaxDiscountPromoCode = "pricingMaxDiscountPromoCode"; + const pricingMaxDiscountPromoCodeCatalogId = + "pricingMaxDiscountPromoCodeCatalogId"; + + before(async () => { + await paymentDatabase["writer"]( + tableNames.singleUseCodePaymentAdjustmentCatalog + ).insert({ + code_value: pricingMaxDiscountPromoCode, + adjustment_exclusivity: "exclusive", + adjustment_name: "Pricing Max Discount Promo Code", + catalog_id: pricingMaxDiscountPromoCodeCatalogId, + maximum_discount_amount: 10_00, + operator: "multiply", + operator_magnitude: "0.50", + adjustment_start_date: "2023-09-20T16:47:37.660Z", // in the past + }); + }); + + it("returns the expected adjustment when within maximum discount amount", async () => { + const { + actualPaymentAmount, + adjustments, + finalPrice, + inclusiveAdjustments, + quotedPaymentAmount, + } = await pricing.getWCForPayment({ + payment: new Payment({ amount: 1000, type: "usd" }), + promoCodes: [pricingMaxDiscountPromoCode], + userAddress: "userAddress", + }); + + expect(finalPrice.winc.toString()).to.equal("1091168091168"); + expect(actualPaymentAmount).to.equal(500); + expect(quotedPaymentAmount).to.equal(1000); + expect(adjustments.map(removeCatalogIdMap)).to.deep.equal([ + { + adjustmentAmount: -500, + currencyType: "usd", + description: "", + maxDiscount: 10_00, + name: "Pricing Max Discount Promo Code", + operator: "multiply", + operatorMagnitude: 0.5, + promoCode: "pricingMaxDiscountPromoCode", + }, + ]); + expect(inclusiveAdjustments.map(removeCatalogIdMap)).to.deep.equal([ + { + adjustmentAmount: -234, + currencyType: "usd", + description: + "Inclusive usage fee on all payments to cover infrastructure costs and payment provider fees.", + name: "Turbo Infrastructure Fee", + operator: "multiply", + operatorMagnitude: 0.766, + }, + ]); + }); + + it("returns the expected adjustment when above maximum discount amount", async () => { + const { + actualPaymentAmount, + adjustments, + finalPrice, + inclusiveAdjustments, + quotedPaymentAmount, + } = await pricing.getWCForPayment({ + payment: new Payment({ amount: 100_00, type: "usd" }), + promoCodes: [pricingMaxDiscountPromoCode], + userAddress: "userAddress", + }); + + expect(finalPrice.winc.toString()).to.equal("10911680911681"); + expect(actualPaymentAmount).to.equal(90_00); + expect(quotedPaymentAmount).to.equal(100_00); + expect(adjustments.map(removeCatalogIdMap)).to.deep.equal([ + { + adjustmentAmount: -10_00, + currencyType: "usd", + description: "", + maxDiscount: 10_00, + name: "Pricing Max Discount Promo Code", + operator: "multiply", + operatorMagnitude: 0.5, + promoCode: "pricingMaxDiscountPromoCode", + }, + ]); + expect(inclusiveAdjustments.map(removeCatalogIdMap)).to.deep.equal([ + { + adjustmentAmount: -2340, + currencyType: "usd", + description: + "Inclusive usage fee on all payments to cover infrastructure costs and payment provider fees.", + name: "Turbo Infrastructure Fee", + operator: "multiply", + operatorMagnitude: 0.766, + }, + ]); + }); + }); + }); +}); diff --git a/src/pricing/pricing.ts b/src/pricing/pricing.ts index 7fafe81..38de104 100644 --- a/src/pricing/pricing.ts +++ b/src/pricing/pricing.ts @@ -14,36 +14,77 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ +import BigNumber from "bignumber.js"; import winston from "winston"; import { CurrencyLimitation, CurrencyLimitations, + oneARInWinston, + oneGiBInBytes, paymentAmountLimits, - turboFeePercentageAsADecimal, } from "../constants"; -import { Adjustment, CurrencyType } from "../database/dbTypes"; +import { Database } from "../database/database"; +import { + Adjustment, + CurrencyType, + PaymentAdjustment, + PaymentAdjustmentCatalog, + PaymentAmount, + SingleUseCodePaymentCatalog, + UploadAdjustment, +} from "../database/dbTypes"; +import { PaymentAmountTooSmallForPromoCode } from "../database/errors"; +import { PostgresDatabase } from "../database/postgres"; import defaultLogger from "../logger"; -import { ByteCount, WC, Winston } from "../types"; +import { ByteCount, W, Winston } from "../types"; import { Payment } from "../types/payment"; import { SupportedPaymentCurrencyTypes, + supportedPaymentCurrencyTypes, zeroDecimalCurrencyTypes, } from "../types/supportedCurrencies"; import { roundToArweaveChunkSize } from "../utils/roundToChunkSize"; import { ReadThroughArweaveToFiatOracle } from "./oracles/arweaveToFiatOracle"; import { ReadThroughBytesToWinstonOracle } from "./oracles/bytesToWinstonOracle"; +import { FinalPrice, NetworkPrice, SubtotalPrice } from "./price"; export type WincForBytesResponse = { - winc: WC; - adjustments: Adjustment[]; + finalPrice: FinalPrice; + networkPrice: NetworkPrice; + adjustments: UploadAdjustment[]; +}; + +export type WincForPaymentResponse = { + finalPrice: FinalPrice; + quotedPaymentAmount: PaymentAmount; + actualPaymentAmount: PaymentAmount; + adjustments: PaymentAdjustment[]; + inclusiveAdjustments: PaymentAdjustment[]; +}; + +export type WincForPaymentParams = { + payment: Payment; + promoCodes?: string[]; + userAddress?: string; }; export interface PricingService { - getWCForPayment: (payment: Payment) => Promise; + getWCForPayment: ( + params: WincForPaymentParams + ) => Promise; getCurrencyLimitations: () => Promise; getFiatPriceForOneAR: (currency: CurrencyType) => Promise; + getFiatRatesForOneGiB: () => Promise<{ + winc: Winston; + fiat: Record; + adjustments: Adjustment[]; + }>; getWCForBytes: (bytes: ByteCount) => Promise; + convertFromUSDAmount: (params: { + amount: number; + type: CurrencyType; + }) => Promise; } /** Stripe accepts 8 digits on all currency types except IDR */ @@ -56,21 +97,25 @@ export class TurboPricingService implements PricingService { private logger: winston.Logger; private readonly bytesToWinstonOracle: ReadThroughBytesToWinstonOracle; private readonly arweaveToFiatOracle: ReadThroughArweaveToFiatOracle; + private readonly paymentDatabase: Database; constructor({ bytesToWinstonOracle, arweaveToFiatOracle, logger = defaultLogger, + paymentDatabase, }: { bytesToWinstonOracle?: ReadThroughBytesToWinstonOracle; arweaveToFiatOracle?: ReadThroughArweaveToFiatOracle; logger?: winston.Logger; + paymentDatabase?: Database; }) { this.logger = logger.child({ class: this.constructor.name }); this.bytesToWinstonOracle = bytesToWinstonOracle ?? new ReadThroughBytesToWinstonOracle({}); this.arweaveToFiatOracle = arweaveToFiatOracle ?? new ReadThroughArweaveToFiatOracle({}); + this.paymentDatabase = paymentDatabase ?? new PostgresDatabase(); } private isWithinTenPercent(value: number, targetValue: number): boolean { @@ -96,37 +141,57 @@ export class TurboPricingService implements PricingService { return this.countDigits(amount) <= maxStripeDigits; } + public async convertFromUSDAmount({ + amount, + type, + }: { + amount: number; + type: CurrencyType; + }): Promise { + if (type === "usd") { + return amount; + } + const usdPriceOfOneAR = await this.arweaveToFiatOracle.getFiatPriceForOneAR( + "usd" + ); + const priceOfOneAR = await this.arweaveToFiatOracle.getFiatPriceForOneAR( + type + ); + const isZeroDecimalCurrency = zeroDecimalCurrencyTypes.includes(type); + + return Math.round( + (amount / + (isZeroDecimalCurrency ? usdPriceOfOneAR * 100 : usdPriceOfOneAR)) * + priceOfOneAR + ); + } + private async getDynamicCurrencyLimitation( curr: string, { maximumPaymentAmount: currMax, minimumPaymentAmount: currMin, suggestedPaymentAmounts: currSuggested, - }: CurrencyLimitation, - usdPriceOfOneAR: number + }: CurrencyLimitation ): Promise { - const currencyPriceOfOneAr = - await this.arweaveToFiatOracle.getFiatPriceForOneAR(curr); - - const convertFromUSDLimit = (amount: number) => - (amount / - (zeroDecimalCurrencyTypes.includes(curr) - ? // Use the DOLLAR value for zero decimal currencies rather than CENT value - usdPriceOfOneAR * 100 - : usdPriceOfOneAR)) * - currencyPriceOfOneAr; + const convertFromUSDLimit = async (amount: number) => + await this.convertFromUSDAmount({ + amount, + type: curr, + }); const multiplier = (val: number) => Math.pow(10, this.countDigits(val) - 2); - const rawMin = convertFromUSDLimit( - paymentAmountLimits.usd.minimumPaymentAmount + const [rawMin, rawMax] = await Promise.all( + [ + paymentAmountLimits.usd.minimumPaymentAmount, + paymentAmountLimits.usd.maximumPaymentAmount, + ].map((amt) => convertFromUSDLimit(amt)) ); + const dynamicMinimum = Math.ceil(rawMin / multiplier(rawMin)) * multiplier(rawMin); - const rawMax = convertFromUSDLimit( - paymentAmountLimits.usd.maximumPaymentAmount - ); const dynamicMaximum = Math.floor(rawMax / multiplier(rawMax)) * multiplier(rawMax); @@ -180,76 +245,323 @@ export class TurboPricingService implements PricingService { } public async getCurrencyLimitations(): Promise { - const usdPriceOfOneAR = await this.arweaveToFiatOracle.getFiatPriceForOneAR( - "usd" - ); - const limits: Partial = {}; await Promise.all( Object.entries(paymentAmountLimits).map(async ([curr, currLimits]) => { limits[curr as SupportedPaymentCurrencyTypes] = - await this.getDynamicCurrencyLimitation( - curr, - currLimits, - usdPriceOfOneAR - ); + await this.getDynamicCurrencyLimitation(curr, currLimits); }) ); return limits as CurrencyLimitations; } - public async getFiatPriceForOneAR(currency: CurrencyType): Promise { - return await this.arweaveToFiatOracle.getFiatPriceForOneAR(currency); + public getFiatPriceForOneAR(currency: CurrencyType): Promise { + return this.arweaveToFiatOracle.getFiatPriceForOneAR(currency); + } + + public async getFiatRatesForOneGiB() { + const { adjustments: uploadAdjustments, finalPrice } = + await this.getWCForBytes(oneGiBInBytes); + + const adjustmentCatalogs: PaymentAdjustmentCatalog[] = + await this.paymentDatabase.getPaymentAdjustmentCatalogs(); + + const fiat: Record = {}; + + // Calculate fiat prices for one GiB + await Promise.all( + supportedPaymentCurrencyTypes.map(async (currency) => { + const fiatPriceOfOneAR = + await this.arweaveToFiatOracle.getFiatPriceForOneAR(currency); + + const { paymentAmountAfterAdjustments: fiatPriceAfterAdjustments } = + await this.applyPaymentAdjustments({ + adjustmentCatalogs, + paymentAmount: fiatPriceOfOneAR, + currencyType: currency, + forRatesEndpoint: true, + }); + + const fiatPriceForOneGiB = finalPrice.winc.times( + fiatPriceAfterAdjustments + ); + + fiat[currency] = +fiatPriceForOneGiB / oneARInWinston; + }) + ); + + return { + winc: finalPrice.winc, + fiat, + adjustments: uploadAdjustments, + }; } - public async getWCForPayment(payment: Payment): Promise { + public async getWCForPayment({ + payment, + promoCodes = [], + userAddress, + }: WincForPaymentParams): Promise { + const adjustmentCatalogs: PaymentAdjustmentCatalog[] = + await this.paymentDatabase.getPaymentAdjustmentCatalogs(); + // if there is a userAddress, we can check if they are eligible a promo code + if (promoCodes.length > 0 && userAddress) { + const singleUseCodeAdjustmentCatalogs = + await this.paymentDatabase.getSingleUsePromoCodeAdjustments( + promoCodes, + userAddress + ); + + if (singleUseCodeAdjustmentCatalogs.length > 0) { + for (const catalog of singleUseCodeAdjustmentCatalogs) { + if ( + (await this.convertFromUSDAmount(payment)) < + catalog.minimumPaymentAmount + ) { + throw new PaymentAmountTooSmallForPromoCode( + catalog.codeValue, + catalog.minimumPaymentAmount + ); + } + adjustmentCatalogs.push(catalog); + } + } + } + + const exclusiveAdjustments = adjustmentCatalogs.filter( + (a) => a.exclusivity === "exclusive" + ); + + const { + adjustments, + paymentAmountAfterAdjustments: paymentAmountAfterExclusiveAdjustments, + } = await this.applyPaymentAdjustments({ + adjustmentCatalogs: exclusiveAdjustments, + paymentAmount: payment.amount, + currencyType: payment.type, + }); + + const inclusiveAdjustmentCatalogs = adjustmentCatalogs.filter( + (a) => a.exclusivity === "inclusive" + ); + const { + adjustments: inclusiveAdjustments, + paymentAmountAfterAdjustments: paymentAmountAfterInclusiveAdjustments, + } = await this.applyPaymentAdjustments({ + adjustmentCatalogs: inclusiveAdjustmentCatalogs, + paymentAmount: payment.amount, + currencyType: payment.type, + }); + const fiatPriceOfOneAR = await this.arweaveToFiatOracle.getFiatPriceForOneAR(payment.type); - const baseWinstonCreditsFromPayment = payment.winstonCreditAmountForARPrice( - fiatPriceOfOneAR, - turboFeePercentageAsADecimal + const baseWinstonCreditsFromPayment = new Winston( + BigNumber( + (zeroDecimalCurrencyTypes.includes(payment.type) + ? paymentAmountAfterInclusiveAdjustments + : paymentAmountAfterInclusiveAdjustments / 100) / fiatPriceOfOneAR + ) + .times(1_000_000_000_000) + .toFixed(0) ); - return baseWinstonCreditsFromPayment; + const finalPrice = new FinalPrice(baseWinstonCreditsFromPayment); + const quotedPaymentAmount = payment.amount; + + this.logger.info("Calculated adjustments for payment.", { + quotedPaymentAmount, + paymentAmountAfterExclusiveAdjustments, + paymentAmountAfterInclusiveAdjustments, + finalPrice, + adjustments, + }); + + return { + finalPrice, + actualPaymentAmount: paymentAmountAfterExclusiveAdjustments, + adjustments, + inclusiveAdjustments, + quotedPaymentAmount, + }; } async getWCForBytes(bytes: ByteCount): Promise { const chunkSize = roundToArweaveChunkSize(bytes); - const winston = await this.bytesToWinstonOracle.getWinstonForBytes( - chunkSize + const networkPrice = new NetworkPrice( + await this.bytesToWinstonOracle.getWinstonForBytes(chunkSize) ); - const adjustmentMultiplier = - (process.env.SUBSIDIZED_WINC_PERCENTAGE - ? +process.env.SUBSIDIZED_WINC_PERCENTAGE - : 0) / 100; - // round down the subsidy amount to closest full integer for the subsidy amount - const adjustmentAmount = winston.times(adjustmentMultiplier); + const uploadAdjustmentCatalogs = + await this.paymentDatabase.getUploadAdjustmentCatalogs(); + + const adjustments: UploadAdjustment[] = []; + let subtotalPrice: SubtotalPrice = new SubtotalPrice(networkPrice.winc); + + for (const { + catalogId, + name, + operator, + operatorMagnitude, + description, + } of uploadAdjustmentCatalogs) { + const priceBeforeAdjustment = subtotalPrice.winc; + + if (operator === "add") { + subtotalPrice = subtotalPrice.add(W(operatorMagnitude)); + } else { + subtotalPrice = subtotalPrice.multiply(operatorMagnitude); + } + + if ( + subtotalPrice.winc.isNonZeroNegativeInteger() || + +subtotalPrice.winc === 0 + ) { + subtotalPrice = new SubtotalPrice(W(0)); + } + + const adjustmentAmount = subtotalPrice.winc.minus(priceBeforeAdjustment); + + const adjustment: UploadAdjustment = { + name, + description, + operator, + operatorMagnitude, + adjustmentAmount, + catalogId, + }; + adjustments.push(adjustment); + + if (+subtotalPrice.winc === 0) { + break; + } + } - const adjustments: Adjustment[] = [ - { - name: "FWD Research July 2023 Subsidy", - description: `A ${ - adjustmentMultiplier * 100 - }% discount for uploads over 500KiB`, - operator: "multiply", - value: adjustmentMultiplier, - // We DEDUCT the adjustment in this flow so we give the inverse by multiplying by negative one - adjustmentAmount: adjustmentAmount.times(-1), - }, - ]; this.logger.info("Calculated adjustments for bytes.", { bytes, - originalAmount: winston.toString(), + originalAmount: networkPrice.toString(), adjustments, }); + const finalPrice = FinalPrice.fromSubtotal(subtotalPrice); + return { - winc: winston.minus(adjustmentAmount), + finalPrice, + networkPrice, adjustments, }; } + + private async applyPaymentAdjustments({ + adjustmentCatalogs, + paymentAmount, + currencyType, + forRatesEndpoint = false, + }: { + adjustmentCatalogs: ( + | PaymentAdjustmentCatalog + | SingleUseCodePaymentCatalog + )[]; + paymentAmount: PaymentAmount; + currencyType: CurrencyType; + forRatesEndpoint?: boolean; + }): Promise<{ + paymentAmountAfterAdjustments: PaymentAmount; + adjustments: PaymentAdjustment[]; + }> { + const adjustments = []; + let paymentAmountAfterAdjustments = paymentAmount; + + for (const catalog of adjustmentCatalogs) { + const { + operator, + operatorMagnitude, + name: adjustmentName, + description, + catalogId, + } = catalog; + const amountBeforeAdjustment = paymentAmountAfterAdjustments; + + switch (operator) { + case "add": + if (currencyType === "usd") { + paymentAmountAfterAdjustments += forRatesEndpoint + ? -operatorMagnitude + : operatorMagnitude; + } else { + paymentAmountAfterAdjustments += await this.convertFromUSDAmount({ + amount: forRatesEndpoint ? -operatorMagnitude : operatorMagnitude, + type: currencyType, + }); + } + break; + case "multiply": + // eslint-disable-next-line no-case-declarations + let calculatedPaymentAmountAfterAdjustments = forRatesEndpoint + ? paymentAmountAfterAdjustments / operatorMagnitude + : Math.round(paymentAmountAfterAdjustments * operatorMagnitude); + + if ((catalog as SingleUseCodePaymentCatalog).maximumDiscountAmount) { + // If theres a max discount amount, we need to check if the calculated amount is greater than the max discount amount + const calculatedAdjustmentAmount = + amountBeforeAdjustment - calculatedPaymentAmountAfterAdjustments; + if ( + (await this.convertFromUSDAmount({ + amount: calculatedAdjustmentAmount, + type: currencyType, + })) > + (catalog as SingleUseCodePaymentCatalog).maximumDiscountAmount + ) { + // If the calculated adjustment amount is greater than the max discount amount, we need to instead set the calculated amount subtract the max discount amount + calculatedPaymentAmountAfterAdjustments = + paymentAmountAfterAdjustments - + (await this.convertFromUSDAmount({ + amount: (catalog as SingleUseCodePaymentCatalog) + .maximumDiscountAmount, + type: currencyType, + })); + } + } + + paymentAmountAfterAdjustments = + calculatedPaymentAmountAfterAdjustments; + + break; + default: + this.logger.warn("Unknown operator from database!", { operator }); + continue; + } + + if (paymentAmountAfterAdjustments < 0) { + paymentAmountAfterAdjustments = 0; + } + + const adjustment: PaymentAdjustment = { + adjustmentAmount: + paymentAmountAfterAdjustments - amountBeforeAdjustment, + currencyType, + catalogId, + description, + name: adjustmentName, + operator, + operatorMagnitude, + }; + if ((catalog as SingleUseCodePaymentCatalog).codeValue) { + adjustment["promoCode"] = ( + catalog as SingleUseCodePaymentCatalog + ).codeValue; + } + if ((catalog as SingleUseCodePaymentCatalog).maximumDiscountAmount) { + adjustment["maxDiscount"] = ( + catalog as SingleUseCodePaymentCatalog + ).maximumDiscountAmount; + } + + adjustments.push(adjustment); + } + + return { adjustments, paymentAmountAfterAdjustments }; + } } diff --git a/src/router.ts b/src/router.ts index 6c4b47e..3763504 100644 --- a/src/router.ts +++ b/src/router.ts @@ -19,11 +19,14 @@ import Router from "koa-router"; import * as promClient from "prom-client"; import { verifySignature } from "./middleware"; +import { arweaveCompatiblePrice } from "./routes/arweaveCompatiblePrice"; import { balanceRoute } from "./routes/balance"; +import { checkBalance } from "./routes/checkBalance"; import { countriesHandler } from "./routes/countries"; import { currenciesRoute } from "./routes/currencies"; import { priceRoutes } from "./routes/priceRoutes"; import { fiatToArRateHandler, ratesHandler } from "./routes/rates"; +import { redeem } from "./routes/redeem"; import { refundBalance } from "./routes/refundBalance"; import { reserveBalance } from "./routes/reserveBalance"; import { stripeRoute } from "./routes/stripe/stripeRoute"; @@ -36,11 +39,25 @@ promClient.collectDefaultMetrics({ register: metricsRegistry }); const router = new Router(); +/** + * Note: when we return next(); in our handlers we are telling koa to continue to the next route handler. if any routes having matching paths, then BOTH handlers will be called, which may not be desired. + */ + +// TODO: these can be broken out into separate handlers router.get("/v1/price/:amount", verifySignature, priceRoutes); -router.get("/v1/price/bytes/:amount", verifySignature, priceRoutes); +// also handles /v1/price/bytes/:amount router.get("/v1/price/:currency/:amount", verifySignature, priceRoutes); -router.get("/v1/top-up/:method/:address/:currency/:amount", topUp); +router.get( + "/v1/top-up/:method/:address/:currency/:amount", + verifySignature, + topUp +); + +router.get("/v1/redeem", redeem); + +// TODO: Add API for admin routes that create and manage promotions + router.post("/v1/stripe-webhook", stripeRoute); router.get("/v1/balance", verifySignature, balanceRoute); router.get("/v1/currencies", currenciesRoute); @@ -72,6 +89,8 @@ router.get( router.get("/v1/refund-balance/:walletAddress", refundBalance); +router.get("/v1/check-balance/:walletAddress", checkBalance); + // Health router.get("/health", async (ctx: KoaContext, next: Next) => { ctx.body = "OK"; @@ -87,4 +106,9 @@ router.get("/metrics", async (ctx: KoaContext, next: Next) => { router.get("/openapi.json", swaggerDocsJSON); router.get("/api-docs", swaggerDocs); +// In order to integrate with existing ecosystem tools (e.g. Arconnect), we need to support the following route: +router.get("/price/arweave/:amount", verifySignature, arweaveCompatiblePrice); +// This endpoint will return the price in winc, as a string, without any additional metadata. +// This is the same as the /v1/price/bytes/:amount endpoint, but without the metadata. + export default router; diff --git a/src/routes/arweaveCompatiblePrice.ts b/src/routes/arweaveCompatiblePrice.ts new file mode 100644 index 0000000..3348743 --- /dev/null +++ b/src/routes/arweaveCompatiblePrice.ts @@ -0,0 +1,29 @@ +/** + * Copyright (C) 2022-2023 Permanent Data Solutions, Inc. All Rights Reserved. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +import { Next } from "koa"; + +import { KoaContext } from "../server"; +import { priceBytesHandler } from "./priceBytes"; + +export async function arweaveCompatiblePrice(ctx: KoaContext, next: Next) { + await priceBytesHandler(ctx, next); + const winc = (ctx.body as { winc: string }).winc; + + if (winc) { + ctx.body = winc; + } +} diff --git a/src/routes/checkBalance.ts b/src/routes/checkBalance.ts new file mode 100644 index 0000000..80bd47c --- /dev/null +++ b/src/routes/checkBalance.ts @@ -0,0 +1,115 @@ +/** + * Copyright (C) 2022-2023 Permanent Data Solutions, Inc. All Rights Reserved. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +import { Next } from "koa"; + +import { UserNotFoundWarning } from "../database/errors"; +import { WincForBytesResponse } from "../pricing/pricing"; +import { KoaContext } from "../server"; +import { + validateAuthorizedRoute, + validateByteCount, + validateQueryParameters, +} from "../utils/validators"; + +export async function checkBalance(ctx: KoaContext, next: Next) { + const { paymentDatabase, pricingService, logger } = ctx.state; + const { walletAddress }: { walletAddress: string } = ctx.params; + + if (validateAuthorizedRoute(ctx) === false) { + return next(); + } + + const { byteCount: rawByteCount } = ctx.query; + const queryParameters = [rawByteCount]; + if (!validateQueryParameters(ctx, queryParameters)) { + return next(); + } + + const [stringByteCount] = queryParameters; + + const byteCount = validateByteCount(ctx, stringByteCount); + if (!byteCount) { + return next(); + } + + let priceWithAdjustments: WincForBytesResponse; + try { + logger.info("Getting base credit amount for byte count...", { + walletAddress, + byteCount, + }); + priceWithAdjustments = await pricingService.getWCForBytes(byteCount); + } catch (error) { + ctx.response.status = 502; + ctx.body = "Error getting base credit amount"; + logger.error("Error getting base credit amount", { + walletAddress, + byteCount, + error, + }); + return next(); + } + const { finalPrice, adjustments } = priceWithAdjustments; + + try { + const userBalance = await paymentDatabase.getBalance(walletAddress); + + if (finalPrice.winc.isGreaterThan(userBalance)) { + ctx.response.status = 402; + ctx.response.message = "Insufficient balance"; + ctx.body = { + userHasSufficientBalance: false, + bytesCostInWinc: finalPrice.winc.toString(), + userBalanceInWinc: userBalance.toString(), + adjustments: adjustments, + }; + return next(); + } + + ctx.response.status = 200; + ctx.response.message = "User has sufficient balance"; + ctx.body = { + userHasSufficientBalance: true, + bytesCostInWinc: finalPrice.winc.toString(), + userBalanceInWinc: userBalance.toString(), + adjustments: adjustments, + }; + } catch (error: UserNotFoundWarning | unknown) { + if (error instanceof UserNotFoundWarning) { + ctx.response.status = 404; + ctx.response.message = "User not found"; + ctx.body = { + userHasSufficientBalance: false, + bytesCostInWinc: finalPrice.winc.toString(), + userBalanceInWinc: "0", + adjustments: adjustments, + }; + logger.info(error.message, { walletAddress, byteCount }); + } else { + logger.error("Error checking balance", { + walletAddress, + byteCount, + error, + }); + + ctx.response.status = 502; + ctx.body = "Error checking balance"; + } + } + + return next(); +} diff --git a/src/routes/priceBytes.ts b/src/routes/priceBytes.ts index 9ca4ec3..b81b416 100644 --- a/src/routes/priceBytes.ts +++ b/src/routes/priceBytes.ts @@ -47,11 +47,12 @@ export async function priceBytesHandler(ctx: KoaContext, next: Next) { } try { const priceWithAdjustments = await pricingService.getWCForBytes(bytes); + const { adjustments, finalPrice } = priceWithAdjustments; ctx.response.status = 200; ctx.set("Cache-Control", `max-age=${oneMinuteInSeconds}`); ctx.body = { - winc: priceWithAdjustments.winc.toString(), - adjustments: priceWithAdjustments.adjustments, + winc: finalPrice.winc.toString(), + adjustments, }; logger.info("Successfully calculated price for byte count", { ...priceWithAdjustments, diff --git a/src/routes/priceFiat.ts b/src/routes/priceFiat.ts index 14824c6..4fa94a7 100644 --- a/src/routes/priceFiat.ts +++ b/src/routes/priceFiat.ts @@ -17,13 +17,27 @@ import { Next } from "koa"; import { oneMinuteInSeconds } from "../constants"; -import { PaymentValidationError } from "../database/errors"; +import { PaymentValidationError, PromoCodeError } from "../database/errors"; import { KoaContext } from "../server"; import { Payment } from "../types/payment"; +import { parseQueryParams } from "../utils/parseQueryParams"; export async function priceFiatHandler(ctx: KoaContext, next: Next) { const logger = ctx.state.logger; const { pricingService } = ctx.state; + const { destinationAddress: rawDestinationAddress, promoCode } = ctx.query; + + const promoCodes = parseQueryParams(promoCode); + const [destinationAddress] = parseQueryParams(rawDestinationAddress); + + const walletAddress = destinationAddress || ctx.state.walletAddress; + + if (promoCodes.length > 0 && !walletAddress) { + ctx.response.status = 400; + ctx.body = + "Promo codes must be applied to a specific `destinationAddress` or to the request signer"; + return next(); + } let payment: Payment; try { @@ -36,23 +50,65 @@ export async function priceFiatHandler(ctx: KoaContext, next: Next) { ctx.body = (error as PaymentValidationError).message; return next(); } - logger.info("Payment Price GET Route :", { payment }); + + logger.info("Payment Price GET Route :", { + payment, + walletAddress, + promoCodes, + }); try { - const winstonCreditAmount = await pricingService.getWCForPayment(payment); + const wincForPaymentResponse = await pricingService.getWCForPayment({ + payment, + promoCodes, + userAddress: walletAddress, + }); + + const { + actualPaymentAmount, + adjustments, + finalPrice, + quotedPaymentAmount, + inclusiveAdjustments, + } = wincForPaymentResponse; logger.info("Base credit amount found for payment", { payment, - winstonCreditAmount, + wincForPaymentResponse, }); - ctx.body = { winc: winstonCreditAmount.toString() }; + ctx.body = { + winc: finalPrice.toString(), + adjustments: adjustments.map((adjustment) => ({ + ...adjustment, + catalogId: undefined, + })), + fees: inclusiveAdjustments.map((adjustment) => ({ + ...adjustment, + catalogId: undefined, + })), + actualPaymentAmount, + quotedPaymentAmount, + }; ctx.set("Cache-Control", `max-age=${oneMinuteInSeconds}`); ctx.response.status = 200; } catch (error) { - logger.error("Failed to get price for payment!", { payment }, error); - ctx.response.status = 502; - ctx.body = "Fiat Oracle Unavailable"; + if (error instanceof PromoCodeError) { + logger.warn("Failed to get price with Promo Code:", { + payment, + message: error.message, + }); + ctx.response.status = 400; + ctx.body = error.message; + } else { + logger.error( + "Failed to get price for payment!", + { payment, error }, + error + ); + ctx.response.status = 502; + ctx.body = "Fiat Oracle Unavailable"; + } } return next(); diff --git a/src/routes/priceRoutes.ts b/src/routes/priceRoutes.ts index 460cdb5..0f95fca 100644 --- a/src/routes/priceRoutes.ts +++ b/src/routes/priceRoutes.ts @@ -25,7 +25,6 @@ export async function priceRoutes(ctx: KoaContext, next: Next) { const walletAddress = ctx.state.walletAddress; if (walletAddress) { - // eslint-disable-next-line // TODO: Put any promotional info from the DB that may change pricing calculations into state } diff --git a/src/routes/rates.ts b/src/routes/rates.ts index 98c9dc1..dd6a59c 100644 --- a/src/routes/rates.ts +++ b/src/routes/rates.ts @@ -16,12 +16,7 @@ */ import { Next } from "koa"; -import { - oneARInWinston, - oneGiBInBytes, - oneMinuteInSeconds, - turboFeePercentageAsADecimal, -} from "../constants"; +import { oneMinuteInSeconds } from "../constants"; import { KoaContext } from "../server"; import { supportedPaymentCurrencyTypes } from "../types/supportedCurrencies"; @@ -29,35 +24,11 @@ export async function ratesHandler(ctx: KoaContext, next: Next) { const { pricingService, logger } = ctx.state; try { - const priceWithAdjustments = await pricingService.getWCForBytes( - oneGiBInBytes - ); - const fiat: Record = {}; - - // Calculate fiat prices for one GiB - await Promise.all( - supportedPaymentCurrencyTypes.map(async (currency) => { - const fiatPriceForOneAR = await pricingService.getFiatPriceForOneAR( - currency - ); - - const fiatPriceForOneGiB = - priceWithAdjustments.winc.times(fiatPriceForOneAR); - const fiatValue = - (fiatPriceForOneGiB.toBigNumber().toNumber() / oneARInWinston) * - (1 + turboFeePercentageAsADecimal); - - fiat[currency] = fiatValue; - }) - ); - const rates = { - winc: priceWithAdjustments.winc.toString(), - fiat, - adjustments: priceWithAdjustments.adjustments, - }; + // TODO: applying adjustments on the generic /rates endpoint might not be the best idea, we may want to just show the raw rates for 1 GiB unadjusted, then return + const rates = await pricingService.getFiatRatesForOneGiB(); ctx.status = 200; ctx.set("Cache-Control", `max-age=${oneMinuteInSeconds}`); - ctx.body = rates; + ctx.body = { ...rates, winc: rates.winc.toString() }; logger.info("Successfully calculated rates.", { rates }); } catch (error) { ctx.status = 502; diff --git a/src/routes/redeem.ts b/src/routes/redeem.ts new file mode 100644 index 0000000..3ba3ced --- /dev/null +++ b/src/routes/redeem.ts @@ -0,0 +1,107 @@ +/** + * Copyright (C) 2022-2023 Permanent Data Solutions, Inc. All Rights Reserved. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +import { Next } from "koa"; +import validator from "validator"; + +import { User } from "../database/dbTypes"; +import { GiftRedemptionError } from "../database/errors"; +import { KoaContext } from "../server"; +import { isValidArweaveBase64URL } from "../utils/base64"; +import { validateSingularQueryParameter } from "../utils/validators"; + +export async function redeem(ctx: KoaContext, next: Next): Promise { + const { paymentDatabase, logger: _logger } = ctx.state; + let logger = _logger; + + const { + email: rawEmail, + destinationAddress: rawDestinationAddress, + id: rawPaymentReceiptId, + } = ctx.query; + + const email = validateSingularQueryParameter(ctx, rawEmail); + const destinationAddress = validateSingularQueryParameter( + ctx, + rawDestinationAddress + ); + const paymentReceiptId = validateSingularQueryParameter( + ctx, + rawPaymentReceiptId + ); + if (!email || !destinationAddress || !paymentReceiptId) { + return next(); + } + + if (!isValidArweaveBase64URL(destinationAddress)) { + ctx.response.status = 400; + ctx.body = + "Provided destination address is not a valid Arweave native address!"; + logger.info("top-up GET -- Invalid destination address", ctx.params); + return next(); + } + + if (!validator.isEmail(email)) { + ctx.response.status = 400; + ctx.body = "Provided recipient email address is not a valid email!"; + logger.info("top-up GET -- Invalid destination address", ctx.params); + return next(); + } + const recipientEmail = validator.escape(email); + + logger = logger.child({ + destinationAddress, + paymentReceiptId, + }); + + let user: User; + try { + logger.info("Redeeming payment receipt"); + user = await paymentDatabase.redeemGift({ + destinationAddress, + paymentReceiptId, + recipientEmail, + }); + } catch (error) { + if (error instanceof GiftRedemptionError) { + ctx.response.status = 400; + ctx.body = error.message; + logger.info(error.message); + return next(); + } + const message = + "Error while redeeming payment receipt. Unable to reach Database!"; + logger.error("Error redeeming payment receipt"); + ctx.response.status = 503; + ctx.body = message; + return next(); + } + + const message = `Payment receipt redeemed for ${user.winstonCreditBalance} winc!`; + + logger = logger.child({ user }); + logger.info(message); + + ctx.response.status = 200; + ctx.body = { + message, + userBalance: user.winstonCreditBalance, + userAddress: user.userAddress, + userCreationDate: user.userCreationDate, + }; + + return next(); +} diff --git a/src/routes/refundBalance.ts b/src/routes/refundBalance.ts index 75407bc..265c141 100644 --- a/src/routes/refundBalance.ts +++ b/src/routes/refundBalance.ts @@ -18,41 +18,34 @@ import { Next } from "koa"; import { UserNotFoundWarning } from "../database/errors"; import { KoaContext } from "../server"; -import { Winston } from "../types/winston"; +import { + validateAuthorizedRoute, + validateQueryParameters, + validateWinstonCreditAmount, +} from "../utils/validators"; export async function refundBalance(ctx: KoaContext, next: Next) { const { paymentDatabase, logger } = ctx.state; const { walletAddress } = ctx.params; - const { winstonCredits, dataItemId } = ctx.query; - - if (!ctx.request.headers.authorization || !ctx.state.user) { - ctx.response.status = 401; - ctx.body = "Unauthorized"; - logger.error("GET Refund balance route with no AUTHORIZATION!"); + if (validateAuthorizedRoute(ctx) === false) { return next(); } - if ( - Array.isArray(dataItemId) || - !winstonCredits || - Array.isArray(winstonCredits) - ) { - ctx.response.status = 400; - ctx.body = "Invalid parameters"; - logger.error("GET Refund balance route with invalid parameters!", { - query: ctx.query, - params: ctx.params, - }); + const { winstonCredits, dataItemId: rawDataItemId } = ctx.query; + const queryParameters = [winstonCredits, rawDataItemId]; + if (!validateQueryParameters(ctx, queryParameters)) { return next(); } - let winstonCreditsToRefund: Winston; - try { - winstonCreditsToRefund = new Winston(+winstonCredits); - } catch (error) { - ctx.response.status = 400; - ctx.body = `Invalid value provided for winstonCredits: ${winstonCredits}`; + // TODO: do some regex validation on the dataItemId + const [stringWinstonCredits, dataItemId] = queryParameters; + + const winstonCreditsToRefund = validateWinstonCreditAmount( + ctx, + stringWinstonCredits + ); + if (!winstonCreditsToRefund) { return next(); } diff --git a/src/routes/reserveBalance.ts b/src/routes/reserveBalance.ts index 63b0463..b8cc60f 100644 --- a/src/routes/reserveBalance.ts +++ b/src/routes/reserveBalance.ts @@ -18,52 +18,31 @@ import { Next } from "koa"; import { InsufficientBalance, UserNotFoundWarning } from "../database/errors"; import { KoaContext } from "../server"; -import { ByteCount } from "../types"; +import { + validateAuthorizedRoute, + validateByteCount, + validateQueryParameters, +} from "../utils/validators"; export async function reserveBalance(ctx: KoaContext, next: Next) { const { paymentDatabase, pricingService, logger } = ctx.state; const { walletAddress } = ctx.params; - const { byteCount: rawByteCount, dataItemId } = ctx.query; - - if (!ctx.request.headers.authorization || !ctx.state.user) { - ctx.response.status = 401; - ctx.body = "Unauthorized"; - logger.error( - "Unable to reserve balance. No authorization or user provided.", - { - user: ctx.state.user, - headers: ctx.request.headers, - } - ); + if (!validateAuthorizedRoute(ctx)) { return next(); } - // validate we have what we need - if ( - Array.isArray(dataItemId) || - !rawByteCount || - Array.isArray(rawByteCount) - ) { - ctx.response.status = 400; - ctx.body = "Missing parameters"; - logger.error("GET Reserve balance route with missing parameters!", { - ...ctx.params, - ...ctx.query, - }); + const { byteCount: rawByteCount, dataItemId: rawDataItemId } = ctx.query; + const queryParameters = [rawByteCount, rawDataItemId]; + if (!validateQueryParameters(ctx, queryParameters)) { return next(); } - let byteCount: ByteCount; - try { - byteCount = ByteCount(+rawByteCount); - } catch (error) { - ctx.response.status = 400; - ctx.body = `Invalid parameter for byteCount: ${rawByteCount}`; - logger.error("GET Reserve balance route with invalid parameters!", { - ...ctx.params, - ...ctx.query, - }); + // TODO: do some regex validation on the dataItemId + const [stringByteCount, dataItemId] = queryParameters; + + const byteCount = validateByteCount(ctx, stringByteCount); + if (!byteCount) { return next(); } @@ -74,6 +53,7 @@ export async function reserveBalance(ctx: KoaContext, next: Next) { dataItemId, }); const priceWithAdjustments = await pricingService.getWCForBytes(byteCount); + const { finalPrice, networkPrice, adjustments } = priceWithAdjustments; logger.info("Reserving balance for user ", { walletAddress, @@ -81,15 +61,17 @@ export async function reserveBalance(ctx: KoaContext, next: Next) { dataItemId, ...priceWithAdjustments, }); - await paymentDatabase.reserveBalance( - walletAddress, - priceWithAdjustments.winc, - dataItemId - ); + await paymentDatabase.reserveBalance({ + userAddress: walletAddress, + dataItemId, + reservedWincAmount: finalPrice, + adjustments, + networkWincAmount: networkPrice, + }); ctx.response.status = 200; ctx.response.message = "Balance reserved"; - - ctx.response.body = priceWithAdjustments.winc; + // TODO: Adjust to JSON response body to Expose adjustments via Reserve balance (e.g: body = { winc, adjustments }), and then to the user of data POST + ctx.body = finalPrice.winc; logger.info("Balance reserved for user!", { walletAddress, byteCount, diff --git a/src/routes/stripe/eventHandlers/disputeCreatedEventHandler.ts b/src/routes/stripe/eventHandlers/disputeCreatedEventHandler.ts index 2ebdba4..340c63e 100644 --- a/src/routes/stripe/eventHandlers/disputeCreatedEventHandler.ts +++ b/src/routes/stripe/eventHandlers/disputeCreatedEventHandler.ts @@ -67,8 +67,10 @@ export async function handleDisputeCreatedEvent( if ( walletBalanceAfterChargeback.isNonZeroNegativeInteger() || + // TODO: we may want to filter within a certain period (e.g. 90/180 days) totalWalletChargebacks.length > maxAllowedChargebackDisputes ) { + // TODO: tag a user in stripe as potentially fraudulent, block payments from card/customer logger.info( "Wallet has suspicious number of chargebacks and/or a negative balance.", { diff --git a/src/routes/stripe/eventHandlers/paymentSuccessEventHandler.ts b/src/routes/stripe/eventHandlers/paymentSuccessEventHandler.ts index 27ab5a5..e966b10 100644 --- a/src/routes/stripe/eventHandlers/paymentSuccessEventHandler.ts +++ b/src/routes/stripe/eventHandlers/paymentSuccessEventHandler.ts @@ -18,13 +18,16 @@ import { randomUUID } from "crypto"; import { Stripe } from "stripe"; import { Database } from "../../../database/database"; +import { EmailProvider } from "../../../emailProvider"; import logger from "../../../logger"; import { MetricRegistry } from "../../../metricRegistry"; +import { triggerEmail } from "../../../triggerEmail"; export async function handlePaymentSuccessEvent( pi: Stripe.PaymentIntent, paymentDatabase: Database, - stripe: Stripe + stripe: Stripe, + emailProvider?: EmailProvider ) { logger.info("💰 Payment Success Event Triggered", pi.metadata); @@ -44,17 +47,22 @@ export async function handlePaymentSuccessEvent( logger.info("Creating payment receipt...", loggerObject); - await paymentDatabase.createPaymentReceipt({ + const maybeUnredeemedGift = await paymentDatabase.createPaymentReceipt({ paymentReceiptId, paymentAmount: pi.amount, currencyType: pi.currency, topUpQuoteId, + senderEmail: pi.receipt_email ?? undefined, }); logger.info(`💸 Payment Receipt created!`, loggerObject); MetricRegistry.paymentSuccessCounter.inc(); MetricRegistry.topUpsCounter.inc(Number(winstonCreditAmount)); + + if (maybeUnredeemedGift) { + await triggerEmail(maybeUnredeemedGift, emailProvider); + } } catch (error) { logger.error("❌ Payment receipt creation has failed!", loggerObject); logger.error(error); diff --git a/src/routes/stripe/stripeRoute.ts b/src/routes/stripe/stripeRoute.ts index 98d300f..63ccb4b 100644 --- a/src/routes/stripe/stripeRoute.ts +++ b/src/routes/stripe/stripeRoute.ts @@ -31,6 +31,7 @@ export async function stripeRoute(ctx: KoaContext, next: Next) { } const stripe = ctx.state.stripe; + const emailProvider = ctx.state.emailProvider; // get the webhook signature and raw body for verification const sig = ctx.request.headers["stripe-signature"] as string; @@ -66,6 +67,7 @@ export async function stripeRoute(ctx: KoaContext, next: Next) { ctx.status = 200; // Unawaited calls so we can return a response immediately. + // TODO - Set the events we want to handle on stripe dashboard switch (event.type) { case "payment_intent.succeeded": @@ -75,7 +77,8 @@ export async function stripeRoute(ctx: KoaContext, next: Next) { handlePaymentSuccessEvent( data.object as Stripe.PaymentIntent, ctx.state.paymentDatabase, - stripe + stripe, + emailProvider ); } catch (error) { logger.error( diff --git a/src/routes/topUp.ts b/src/routes/topUp.ts index e2a049e..71285d7 100644 --- a/src/routes/topUp.ts +++ b/src/routes/topUp.ts @@ -17,28 +17,41 @@ import { randomUUID } from "crypto"; import { Next } from "koa"; import Stripe from "stripe"; +import validator from "validator"; import { CurrencyLimitations, electronicallySuppliedServicesTaxCode, + isGiftingEnabled, paymentIntentTopUpMethod, topUpMethods, } from "../constants"; -import { PaymentValidationError } from "../database/errors"; +import { CreateTopUpQuoteParams } from "../database/dbTypes"; +import { PaymentValidationError, PromoCodeError } from "../database/errors"; import { MetricRegistry } from "../metricRegistry"; +import { WincForPaymentResponse } from "../pricing/pricing"; import { KoaContext } from "../server"; -import { WC } from "../types/arc"; import { Payment } from "../types/payment"; import { winstonToArc } from "../types/winston"; import { isValidArweaveBase64URL } from "../utils/base64"; +import { parseQueryParams } from "../utils/parseQueryParams"; +import { + validateDestinationAddressType, + validateGiftMessage, +} from "../utils/validators"; export async function topUp(ctx: KoaContext, next: Next) { const logger = ctx.state.logger; const { pricingService, paymentDatabase, stripe } = ctx.state; - const { amount, currency, method, address: destinationAddress } = ctx.params; + const { + amount, + currency, + method, + address: rawDestinationAddress, + } = ctx.params; - const loggerObject = { amount, currency, method, destinationAddress }; + const loggerObject = { amount, currency, method, rawDestinationAddress }; if (!topUpMethods.includes(method)) { ctx.response.status = 400; @@ -47,13 +60,54 @@ export async function topUp(ctx: KoaContext, next: Next) { return next(); } - if (!isValidArweaveBase64URL(destinationAddress)) { - ctx.response.status = 403; - ctx.body = "Destination address is not a valid Arweave native address!"; - logger.info("top-up GET -- Invalid destination address", loggerObject); + const { + destinationAddressType: rawAddressType, + giftMessage: rawGiftMessage, + } = ctx.query; + + const destinationAddressType = rawAddressType + ? validateDestinationAddressType(ctx, rawAddressType) + : "arweave"; + if (!destinationAddressType) { + return next(); + } + + const giftMessage = rawGiftMessage + ? validateGiftMessage(ctx, rawGiftMessage) + : undefined; + if (giftMessage === false) { return next(); } + let destinationAddress: string; + if (destinationAddressType === "arweave") { + if (!isValidArweaveBase64URL(rawDestinationAddress)) { + ctx.response.status = 403; + ctx.body = "Destination address is not a valid Arweave native address!"; + logger.info("top-up GET -- Invalid destination address", loggerObject); + return next(); + } + + destinationAddress = rawDestinationAddress; + } else { + if (!isGiftingEnabled) { + ctx.response.status = 403; + ctx.body = "Gifting by email is disabled!"; + logger.info("top-up GET -- Gifting is disabled", loggerObject); + return next(); + } + + if (!validator.isEmail(rawDestinationAddress)) { + ctx.response.status = 400; + ctx.body = "Destination address is not a valid email!"; + logger.info("top-up GET -- Invalid destination address", loggerObject); + return next(); + } + + // Escape email address to prevent XSS + destinationAddress = validator.escape(rawDestinationAddress); + } + let currencyLimitations: CurrencyLimitations; try { @@ -86,13 +140,28 @@ export async function topUp(ctx: KoaContext, next: Next) { return next(); } - let winstonCreditAmount: WC; + const promoCodes = parseQueryParams(ctx.query.promoCode); + + let wincForPaymentResponse: WincForPaymentResponse; try { - winstonCreditAmount = await pricingService.getWCForPayment(payment); - } catch (error: unknown) { - logger.error(error); - ctx.response.status = 502; - ctx.body = "Fiat Oracle Unavailable"; + wincForPaymentResponse = await pricingService.getWCForPayment({ + payment, + userAddress: rawDestinationAddress, + promoCodes, + }); + } catch (error) { + if (error instanceof PromoCodeError) { + logger.warn("Failed to get price with Promo Code:", { + payment, + message: error.message, + }); + ctx.response.status = 400; + ctx.body = error.message; + } else { + logger.error(error); + ctx.response.status = 502; + ctx.body = "Fiat Oracle Unavailable"; + } return next(); } @@ -102,23 +171,44 @@ export async function topUp(ctx: KoaContext, next: Next) { const fiveMinutesMs = oneMinuteMs * 5; const fiveMinutesFromNow = new Date(Date.now() + fiveMinutesMs).toISOString(); - const topUpQuote = { + const { + adjustments, + inclusiveAdjustments, + finalPrice, + actualPaymentAmount, + quotedPaymentAmount, + } = wincForPaymentResponse; + + // TODO: Allow users to top up for free with promo codes + + const topUpQuote: CreateTopUpQuoteParams = { topUpQuoteId: randomUUID(), - destinationAddressType: "arweave", - paymentAmount: payment.amount, - winstonCreditAmount, + destinationAddressType, + paymentAmount: actualPaymentAmount, + quotedPaymentAmount, + winstonCreditAmount: finalPrice.winc, destinationAddress, currencyType: payment.type, quoteExpirationDate: fiveMinutesFromNow, paymentProvider: "stripe", + adjustments, + giftMessage, }; - // Take all of topUpQuote to stripeMetadata except paymentProvider - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const { paymentProvider, ...stripeMetadataRaw } = topUpQuote; - const stripeMetadata = { - ...stripeMetadataRaw, - winstonCreditAmount: winstonCreditAmount.toString(), - }; + + const { paymentProvider, adjustments: _a, ...stripeMetadataRaw } = topUpQuote; + const stripeMetadata = adjustments.reduce( + (acc, curr, i) => { + // Add adjustments to stripe metadata + // Stripe key name in metadata is limited to 40 characters, so we need to truncate the name. + const keyName = `adj${i}_${curr.name}`.slice(0, 40); + acc[keyName] = curr.adjustmentAmount.toString(); + return acc; + }, + { + ...stripeMetadataRaw, + winstonCreditAmount: finalPrice.winc.toString(), + } as Record + ); let intentOrCheckout: | Stripe.Response @@ -127,14 +217,32 @@ export async function topUp(ctx: KoaContext, next: Next) { logger.info(`Creating stripe ${method}...`, loggerObject); if (method === paymentIntentTopUpMethod) { intentOrCheckout = await stripe.paymentIntents.create({ - amount: payment.amount, + amount: actualPaymentAmount, currency: payment.type, metadata: stripeMetadata, }); } else { + const localGiftUrl = `http://localhost:5173`; + const prodGiftUrl = `https://gift.ardrive.io`; + const giftUrl = + process.env.NODE_ENV === "prod" || process.env.NODE_ENV === "dev" + ? prodGiftUrl + : localGiftUrl; + + const urlEncodedGiftMessage = giftMessage + ? encodeURIComponent(giftMessage) + : undefined; intentOrCheckout = await stripe.checkout.sessions.create({ + // TODO: Success and Cancel URLS (Do we need app origin? e.g: ArDrive Widget, Top Up Page, ario-turbo-cli) success_url: "https://app.ardrive.io", - cancel_url: "https://app.ardrive.io", + cancel_url: + destinationAddressType === "email" + ? `${giftUrl}?email=${destinationAddress}&amount=${payment.amount}${ + urlEncodedGiftMessage + ? `&giftMessage=${urlEncodedGiftMessage}` + : "" + }` + : "https://app.ardrive.io", currency: payment.type, automatic_tax: { enabled: !!process.env.ENABLE_AUTO_STRIPE_TAX || false, @@ -146,13 +254,13 @@ export async function topUp(ctx: KoaContext, next: Next) { product_data: { name: "Turbo Credits", description: `${winstonToArc( - winstonCreditAmount + finalPrice.winc )} credits on Turbo to destination address "${destinationAddress}"`, tax_code: electronicallySuppliedServicesTaxCode, metadata: stripeMetadata, }, currency: payment.type, - unit_amount: payment.amount, + unit_amount: actualPaymentAmount, }, quantity: 1, }, @@ -172,7 +280,10 @@ export async function topUp(ctx: KoaContext, next: Next) { } try { - await paymentDatabase.createTopUpQuote(topUpQuote); + await paymentDatabase.createTopUpQuote({ + ...topUpQuote, + adjustments: [...adjustments, ...inclusiveAdjustments], + }); } catch (error) { logger.error(error); ctx.response.status = 503; @@ -183,6 +294,16 @@ export async function topUp(ctx: KoaContext, next: Next) { ctx.body = { topUpQuote, paymentSession: intentOrCheckout, + adjustments: adjustments.map((a) => { + const { catalogId: _, ...adjustmentsWithoutCatalogId } = a; + + return adjustmentsWithoutCatalogId; + }), + fees: inclusiveAdjustments.map((a) => { + const { catalogId: _, ...adjustmentsWithoutCatalogId } = a; + + return adjustmentsWithoutCatalogId; + }), }; ctx.response.status = 200; diff --git a/src/server.ts b/src/server.ts index e5afc86..be7abfa 100644 --- a/src/server.ts +++ b/src/server.ts @@ -21,8 +21,14 @@ import Stripe from "stripe"; import { Logger } from "winston"; import { Architecture } from "./architecture"; -import { TEST_PRIVATE_ROUTE_SECRET, defaultPort } from "./constants"; +import { + TEST_PRIVATE_ROUTE_SECRET, + defaultPort, + isGiftingEnabled, + migrateOnStartup, +} from "./constants"; import { PostgresDatabase } from "./database/postgres"; +import { MandrillEmailProvider } from "./emailProvider"; import logger from "./logger"; import { MetricRegistry } from "./metricRegistry"; import { architectureMiddleware, loggerMiddleware } from "./middleware"; @@ -48,13 +54,13 @@ export async function createServer( await loadSecretsToEnv(); const STRIPE_SECRET_KEY = process.env.STRIPE_SECRET_KEY; + const MANDRILL_API_KEY = process.env.MANDRILL_API_KEY; const sharedSecret = process.env.PRIVATE_ROUTE_SECRET ?? TEST_PRIVATE_ROUTE_SECRET; if (!sharedSecret) { throw new Error("Shared secret not set"); } - if (!STRIPE_SECRET_KEY) { throw new Error("Stripe secret key or webhook secret not set"); } @@ -67,15 +73,31 @@ export async function createServer( app.use(jwt({ secret: sharedSecret, passthrough: true })); const pricingService = arch.pricingService ?? new TurboPricingService({}); - const paymentDatabase = arch.paymentDatabase ?? new PostgresDatabase(); + const paymentDatabase = + arch.paymentDatabase ?? new PostgresDatabase({ migrate: migrateOnStartup }); const stripe = arch.stripe ?? new Stripe(STRIPE_SECRET_KEY, { apiVersion: "2022-11-15" }); + const emailProvider = (() => { + if (!isGiftingEnabled) { + return undefined; + } + if (arch.emailProvider) { + return arch.emailProvider; + } + if (!MANDRILL_API_KEY) { + throw new Error( + "MANDRILL_API_KEY environment variable is not set! Please set the key and restart the server or set GIFTING_ENABLED=false to disable gifting by email on top ups flow." + ); + } + return new MandrillEmailProvider(MANDRILL_API_KEY, logger); + })(); app.use((ctx: KoaContext, next: Next) => architectureMiddleware(ctx, next, { pricingService, paymentDatabase, stripe, + emailProvider, }) ); diff --git a/src/triggerEmail.ts b/src/triggerEmail.ts new file mode 100644 index 0000000..3e7f601 --- /dev/null +++ b/src/triggerEmail.ts @@ -0,0 +1,57 @@ +/** + * Copyright (C) 2022-2023 Permanent Data Solutions, Inc. All Rights Reserved. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +import { isGiftingEnabled } from "./constants"; +import { UnredeemedGift } from "./database/dbTypes"; +import { EmailProvider } from "./emailProvider"; +import logger from "./logger"; +import { MetricRegistry } from "./metricRegistry"; + +export async function triggerEmail( + unredeemedGift: UnredeemedGift, + emailProvider?: EmailProvider +): Promise { + try { + if (!emailProvider) { + throw Error( + "Email provider is not defined! Cannot send gift redemption email!" + ); + } + + if (!isGiftingEnabled) { + throw Error("Gifting is not enabled! Cannot send gift redemption email!"); + } + + const { + giftedWincAmount, + paymentReceiptId, + recipientEmail, + giftMessage, + senderEmail, + } = unredeemedGift; + + await emailProvider.sendEmail({ + credits: (+giftedWincAmount / 1_000_000_000_000).toFixed(4), + giftCode: paymentReceiptId, + recipientEmail, + giftMessage, + senderEmail, + }); + } catch (error) { + MetricRegistry.giftEmailTriggerFailure.inc(); + logger.error("❌ Email sending has failed!", error, unredeemedGift); + } +} diff --git a/src/types/ar.test.ts b/src/types/ar.test.ts index 42008d7..e886c39 100644 --- a/src/types/ar.test.ts +++ b/src/types/ar.test.ts @@ -96,4 +96,6 @@ describe("AR class", () => { ).to.equal("-18014398509481982123456789012"); }); }); + + // TODO: toJSON coverage }); diff --git a/src/types/index.ts b/src/types/index.ts index e8c815f..2b86de7 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -23,6 +23,6 @@ export * from "./byteCount"; export type Base64URLString = string; export type RSAModulusString = Base64URLString; -export type PublicKeyString = RSAModulusString; +export type PublicKeyString = RSAModulusString; // TODO: add other supported public key types (ETH, SOL) export type PublicArweaveAddress = Base64URLString; export type TransactionId = Base64URLString; diff --git a/src/types/payment.test.ts b/src/types/payment.test.ts index e8af0ed..8ed16c1 100644 --- a/src/types/payment.test.ts +++ b/src/types/payment.test.ts @@ -104,39 +104,4 @@ describe("Payment class", () => { ); }); }); - - describe("winstonCreditAmountForARPrice method", () => { - it("returns the expected amount for a two decimal currency", () => { - const payment = new Payment({ amount: 8310, type: "usd" }); - - // Retrieved from `curl https://api.coingecko.com/api/v3/simple/price\?ids\=arweave\&vs_currencies\=usd` on 04-19-2023 - const pricePerAr = 8.31; - - expect( - payment.winstonCreditAmountForARPrice(pricePerAr, 0).toString() - ).to.equal("10000000000000"); - }); - - it("returns the expected amount for a two decimal currency when reduced by a fee", () => { - const payment = new Payment({ amount: 5000, type: "usd" }); - - // Retrieved from `curl https://api.coingecko.com/api/v3/simple/price\?ids\=arweave\&vs_currencies\=usd` on 04-19-2023 - const pricePerAr = 10; - - expect( - payment.winstonCreditAmountForARPrice(pricePerAr, 0.2).toString() - ).to.equal("4000000000000"); - }); - - it("returns the expected amount for a zero decimal currency", () => { - const payment = new Payment({ amount: 11190, type: "jpy" }); - - // Retrieved from `curl https://api.coingecko.com/api/v3/simple/price\?ids\=arweave\&vs_currencies\=jpy` on 04-19-2023 - const pricePerAr = 1119.26; - - expect( - payment.winstonCreditAmountForARPrice(pricePerAr, 0).toString() - ).to.equal("9997677036613"); - }); - }); }); diff --git a/src/types/payment.ts b/src/types/payment.ts index 9676e45..056d97d 100644 --- a/src/types/payment.ts +++ b/src/types/payment.ts @@ -14,8 +14,6 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -import BigNumber from "bignumber.js"; - import { CurrencyLimitations } from "../constants"; import { CurrencyType, PaymentAmount } from "../database/dbTypes"; import { @@ -24,13 +22,11 @@ import { PaymentAmountTooSmall, UnsupportedCurrencyType, } from "../database/errors"; -import { WC } from "./arc"; import { SupportedPaymentCurrencyTypes, supportedPaymentCurrencyTypes, zeroDecimalCurrencyTypes, } from "./supportedCurrencies"; -import { Winston } from "./winston"; interface PaymentConstructorParams { amount: PaymentAmount; @@ -86,21 +82,9 @@ export class Payment { this.type = type; } - public winstonCreditAmountForARPrice( - priceForOneAR: number, - turboFeePercentageAsADecimal: number - ): WC { - const zeroDecimalAmount = zeroDecimalCurrencyTypes.includes(this.type) + public get zeroDecimalAmount(): number { + return zeroDecimalCurrencyTypes.includes(this.type) ? this.amount : this.amount / 100; - - const paymentAmountAfterFees = - zeroDecimalAmount - zeroDecimalAmount * turboFeePercentageAsADecimal; - - const arcForPaymentAmount = paymentAmountAfterFees / priceForOneAR; - - return new Winston( - BigNumber(arcForPaymentAmount).times(1_000_000_000_000).toFixed(0) - ); } } diff --git a/src/types/winston.ts b/src/types/winston.ts index 8931b4d..4b56576 100644 --- a/src/types/winston.ts +++ b/src/types/winston.ts @@ -43,6 +43,7 @@ export class Winston { divisor: BigNumber.Value, round: "ROUND_DOWN" | "ROUND_CEIL" = "ROUND_CEIL" ): Winston { + // TODO: Best rounding strategy? Up or down? return W( this.amount .dividedBy(divisor) diff --git a/src/utils/jwkUtils.ts b/src/utils/jwkUtils.ts index 86f13aa..cb6b606 100644 --- a/src/utils/jwkUtils.ts +++ b/src/utils/jwkUtils.ts @@ -21,9 +21,8 @@ import { createHash } from "crypto"; import { RSAModulusString } from "../types"; import { toB64Url } from "./base64"; -export async function arweaveRSAModulusToAddress( - modulus: RSAModulusString -): Promise { +// TODO: create a factory that returns address for various wallet types (Arweave, ETH, SOL) +export function arweaveRSAModulusToAddress(modulus: RSAModulusString): string { const hash = createHash("sha256"); hash.update(b64UrlToBuffer(modulus)); const result = new Uint8Array(hash.digest()); diff --git a/src/utils/loadSecretsToEnv.ts b/src/utils/loadSecretsToEnv.ts index 1292760..6adbb2c 100644 --- a/src/utils/loadSecretsToEnv.ts +++ b/src/utils/loadSecretsToEnv.ts @@ -30,6 +30,7 @@ const jwtSecretName = "jwt-secret"; const dbPasswordSecretName = "payment-db-password"; const wincSubsidizedPercentageParamName = "/payment-service/subsidized-winc-percentage"; +const mandrillApiKeySecretName = "mandrill-api-key"; export async function loadSecretsToEnv() { try { @@ -83,6 +84,9 @@ export async function loadSecretsToEnv() { ); process.env.JWT_SECRET ??= await getSecretValueCommand(jwtSecretName); process.env.DB_PASSWORD ??= await getSecretValueCommand(dbPasswordSecretName); + process.env.MANDRILL_API_KEY ??= await getSecretValueCommand( + mandrillApiKeySecretName + ); process.env.SUBSIDIZED_WINC_PERCENTAGE ??= await getSSMParameterCommand( wincSubsidizedPercentageParamName diff --git a/src/utils/parseQueryParams.ts b/src/utils/parseQueryParams.ts new file mode 100644 index 0000000..c5049d0 --- /dev/null +++ b/src/utils/parseQueryParams.ts @@ -0,0 +1,31 @@ +/** + * Copyright (C) 2022-2023 Permanent Data Solutions, Inc. All Rights Reserved. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +export function parseQueryParams( + queryParams: string | string[] | undefined +): string[] { + // If no codes, set to empty array, else if string, convert to array, else use array of codes + + const params = + queryParams === undefined + ? [] + : typeof queryParams === "string" + ? [queryParams] + : queryParams; + + return [...new Set(params)]; +} diff --git a/src/utils/validators.ts b/src/utils/validators.ts new file mode 100644 index 0000000..77715df --- /dev/null +++ b/src/utils/validators.ts @@ -0,0 +1,165 @@ +/** + * Copyright (C) 2022-2023 Permanent Data Solutions, Inc. All Rights Reserved. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +import validator from "validator"; + +import { maxGiftMessageLength } from "../constants"; +import { + DestinationAddressType, + destinationAddressTypes, +} from "../database/dbTypes"; +import { MetricRegistry } from "../metricRegistry"; +import { KoaContext } from "../server"; +import { ByteCount, Winston } from "../types"; + +/** Returns true if these given query parameters are strings */ +export function validateQueryParameters( + ctx: KoaContext, + queryParameters: (string | string[] | undefined)[] +): queryParameters is string[] { + if ( + queryParameters.some((parameter) => !parameter) || + queryParameters.some(Array.isArray) + ) { + ctx.response.status = 400; + ctx.body = "Invalid or missing parameters"; + ctx.state.logger.error("Invalid parameters provided for route!", { + query: ctx.query, + params: ctx.params, + }); + return false; + } + return true; +} + +export function validateAuthorizedRoute(ctx: KoaContext): boolean { + if (!ctx.request.headers.authorization || !ctx.state.user) { + ctx.response.status = 401; + ctx.body = "Unauthorized"; + ctx.state.logger.error( + "No authorization or user provided for authorized route!", + { + user: ctx.state.user, + headers: ctx.request.headers, + } + ); + MetricRegistry.unauthorizedProtectedRouteActivity.inc(); + return false; + } + return true; +} + +export function validateByteCount( + ctx: KoaContext, + stringByteCount: string +): ByteCount | false { + try { + return ByteCount(+stringByteCount); + } catch (error) { + ctx.response.status = 400; + ctx.body = `Invalid parameter for byteCount: ${stringByteCount}`; + ctx.state.logger.error("Invalid byte count!", { + ...ctx.params, + ...ctx.query, + error, + }); + return false; + } +} + +export function validateWinstonCreditAmount( + ctx: KoaContext, + stringWinstonCreditAmount: string +): Winston | false { + try { + return new Winston(+stringWinstonCreditAmount); + } catch (error) { + ctx.response.status = 400; + ctx.body = `Invalid value provided for winstonCredits: ${stringWinstonCreditAmount}`; + ctx.state.logger.error("Invalid winston credit amount!", { + ...ctx.params, + ...ctx.query, + error, + }); + return false; + } +} + +export function validateSingularQueryParameter( + ctx: KoaContext, + queryParameter: string | string[] | undefined +): string | false { + if ( + !queryParameter || + (Array.isArray(queryParameter) && queryParameter.length > 1) + ) { + ctx.response.status = 400; + ctx.body = "Invalid or missing parameters"; + ctx.state.logger.error("Invalid parameters provided for route!", { + query: ctx.query, + params: ctx.params, + }); + return false; + } + + return Array.isArray(queryParameter) ? queryParameter[0] : queryParameter; +} + +function isDestinationAddressType( + destinationAddressType: string +): destinationAddressType is DestinationAddressType { + return destinationAddressTypes.includes( + destinationAddressType as DestinationAddressType + ); +} + +export function validateDestinationAddressType( + ctx: KoaContext, + destinationAddressType: string | string[] +): DestinationAddressType | false { + const destType = validateSingularQueryParameter(ctx, destinationAddressType); + + if (!destType || !isDestinationAddressType(destType)) { + ctx.response.status = 400; + ctx.body = `Invalid destination address type: ${destType}`; + ctx.state.logger.error("Invalid destination address type!", { + ...ctx.params, + ...ctx.query, + }); + return false; + } + + return destType; +} + +export function validateGiftMessage( + ctx: KoaContext, + giftMessage: string | string[] +): string | false { + const message = validateSingularQueryParameter(ctx, giftMessage); + + if (!message || message.length > maxGiftMessageLength) { + ctx.response.status = 400; + ctx.body = "Invalid gift message!"; + ctx.state.logger.error("Invalid gift message!", { + query: ctx.query, + params: ctx.params, + }); + return false; + } + + return validator.escape(message); +} diff --git a/src/utils/verifyArweaveSignature.ts b/src/utils/verifyArweaveSignature.ts index d09b635..272dfd2 100644 --- a/src/utils/verifyArweaveSignature.ts +++ b/src/utils/verifyArweaveSignature.ts @@ -26,6 +26,7 @@ export interface VerifySignatureParams { nonce: string; } +// TODO: turn this into a class/factory function that can validate a data signature of other wallets (ETH, SOL) export async function verifyArweaveSignature({ publicKey, signature, diff --git a/tests/dbTestHelper.ts b/tests/dbTestHelper.ts index 3cfe838..0b48b78 100644 --- a/tests/dbTestHelper.ts +++ b/tests/dbTestHelper.ts @@ -19,6 +19,8 @@ import { Knex } from "knex"; import { tableNames } from "../src/database/dbConstants"; import { ChargebackReceiptDBInsert, + PaymentAdjustmentDBInsert, + PaymentAdjustmentDBResult, PaymentReceiptDBInsert, TopUpQuoteDBInsert, UserAddress, @@ -29,8 +31,10 @@ import { PostgresDatabase } from "../src/database/postgres"; export const stubArweaveUserAddress: UserAddress = "1234567890123456789012345678901231234567890"; -const oneHourAgo = new Date(Date.now() - 1000 * 60 * 60).toISOString(); -const oneHourFromNow = new Date(Date.now() + 1000 * 60 * 60).toISOString(); +export const oneHourAgo = new Date(Date.now() - 1000 * 60 * 60).toISOString(); +export const oneHourFromNow = new Date( + Date.now() + 1000 * 60 * 60 +).toISOString(); type StubTopUpQuoteParams = Partial; @@ -39,20 +43,44 @@ function stubTopUpQuoteInsert({ destination_address = stubArweaveUserAddress, destination_address_type = "arweave", payment_amount = "100", + quoted_payment_amount = "150", currency_type = "usd", winston_credit_amount = "1337", quote_expiration_date = oneHourFromNow, payment_provider = "stripe", + gift_message, }: StubTopUpQuoteParams): TopUpQuoteDBInsert { return { top_up_quote_id, destination_address, destination_address_type, payment_amount, + quoted_payment_amount, currency_type, winston_credit_amount, quote_expiration_date, payment_provider, + gift_message, + }; +} + +type StubPaymentAdjustmentParams = Partial; + +function stubPaymentAdjustmentInsert({ + adjusted_currency_type = "usd", + adjusted_payment_amount = "100", + adjustment_index = 0, + catalog_id = "The Stubbiest Catalog", + top_up_quote_id = "The Stubbiest Top Up Quote", + user_address = stubArweaveUserAddress, +}: StubPaymentAdjustmentParams): PaymentAdjustmentDBInsert { + return { + adjusted_currency_type, + adjusted_payment_amount, + adjustment_index, + catalog_id, + top_up_quote_id, + user_address, }; } @@ -100,7 +128,7 @@ export class DbTestHelper { constructor(public readonly db: PostgresDatabase) {} private get knex(): Knex { - return this.db["knexWriter"]; + return this.db["writer"]; } public async insertStubUser(insertParams: StubUserParams): Promise { @@ -115,6 +143,14 @@ export class DbTestHelper { ); } + public async insertStubPaymentAdjustment( + insertParams: StubPaymentAdjustmentParams + ): Promise { + return this.knex(tableNames.paymentAdjustment).insert( + stubPaymentAdjustmentInsert(insertParams) + ); + } + public async insertStubPaymentReceipt( insertParams: StubPaymentReceiptParams ): Promise { diff --git a/tests/helpers/stubs.ts b/tests/helpers/stubs.ts index 1d076f3..dc2acf6 100644 --- a/tests/helpers/stubs.ts +++ b/tests/helpers/stubs.ts @@ -16,9 +16,8 @@ */ import Stripe from "stripe"; -import { Adjustment } from "../../src/database/dbTypes"; - export const stubTxId1 = "0000000000000000000000000000000000000000001"; +export const stubTxId2 = "0000000000000000000000000000000000000000002"; interface StubPaymentIntentParams { id?: string; @@ -152,6 +151,7 @@ export const paymentIntentFailedStub: Stripe.PaymentIntent = paymentIntentStub({ status: "canceled", }); +// eslint-disable-next-line @typescript-eslint/no-explicit-any export const stripeResponseStub: Stripe.Response = ( details: Stripe.PaymentIntent | Stripe.Checkout.Session ) => { @@ -297,30 +297,19 @@ export const expectedArPrices = { }, }; -export const expectedAdjustments: Adjustment[] = [ - { - name: "FWD Research July 2023 Subsidy", - description: "A 0% discount for uploads over 500KiB", - value: 0, - operator: "multiply", - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-expect-error : Winston type is stringified into "0" on API response - adjustmentAmount: "0", - }, -]; - +// TODO: we could make this a function and apply it against the arweave rates above using the turboPercentageFee constant export const expectedRates = { winc: "857922282166", fiat: { - aud: 10.93233205718397, - brl: 36.638085813267686, - cad: 9.908744982331742, - eur: 6.743011761139201, - gbp: 5.8671589032756595, - hkd: 58.02788994445884, - inr: 607.008240275528, - jpy: 995.4331540717822, - sgd: 9.82432542976695, - usd: 7.407815737590149, + aud: 11.603230865848, + brl: 38.886503442302, + cad: 10.516827975899, + eur: 7.156819037912, + gbp: 6.227216565069, + hkd: 61.5889638333, + inr: 644.25931379941, + jpy: 1056.521210460615, + sgd: 10.427227737552, + usd: 7.862420914889, }, }; diff --git a/tests/helpers/testExpectations.ts b/tests/helpers/testExpectations.ts index e9f2b8e..28a6399 100644 --- a/tests/helpers/testExpectations.ts +++ b/tests/helpers/testExpectations.ts @@ -36,79 +36,3 @@ const expectedHeadersWithContentLength = (contentLength: number) => { connection: "close", }; }; - -const expectedDateColumn = (defaultValue = true) => { - return { - type: "timestamp with time zone", - maxLength: null, - nullable: false, - defaultValue: defaultValue ? "CURRENT_TIMESTAMP" : null, - }; -}; - -const expectedVarCharColumn = ({ - length = 255, - nullable = false, - defaultValue = null, -}: { - length?: number; - nullable?: boolean; - defaultValue?: null | string; -}) => { - return { - type: "character varying", - maxLength: length, - nullable, - defaultValue, - }; -}; - -export const expectedColumnInfo = { - user: { - user_address: expectedVarCharColumn({}), - user_address_type: expectedVarCharColumn({}), - user_creation_date: expectedDateColumn(), - winston_credit_balance: expectedVarCharColumn({}), - promotional_info: { - type: "jsonb", - maxLength: null, - nullable: false, - defaultValue: "'{}'::jsonb", - }, - }, - - top_up_quote: { - top_up_quote_id: expectedVarCharColumn({}), - destination_address: expectedVarCharColumn({}), - destination_address_type: expectedVarCharColumn({}), - payment_amount: expectedVarCharColumn({}), - currency_type: expectedVarCharColumn({}), - winston_credit_amount: expectedVarCharColumn({}), - quote_expiration_date: expectedDateColumn(false), - quote_creation_date: expectedDateColumn(), - payment_provider: expectedVarCharColumn({}), - }, - - failed_top_up_quote: { - quote_creation_date: expectedDateColumn(false), - - failed_reason: expectedVarCharColumn({}), - quote_failed_date: expectedDateColumn(), - }, - - payment_receipt: { - quote_creation_date: expectedDateColumn(false), - - payment_receipt_id: expectedVarCharColumn({}), - payment_receipt_date: expectedDateColumn(), - }, - - chargeback_receipt: { - quote_creation_date: expectedDateColumn(false), - payment_receipt_date: expectedDateColumn(false), - - chargeback_receipt_id: expectedVarCharColumn({}), - chargeback_reason: expectedVarCharColumn({}), - chargeback_receipt_date: expectedDateColumn(), - }, -}; diff --git a/tests/helpers/testHelpers.ts b/tests/helpers/testHelpers.ts index 48428a6..321f538 100644 --- a/tests/helpers/testHelpers.ts +++ b/tests/helpers/testHelpers.ts @@ -21,6 +21,7 @@ import Stripe from "stripe"; import { createAxiosInstance } from "../../src/axiosClient"; import { PostgresDatabase } from "../../src/database/postgres"; +import { MandrillEmailProvider } from "../../src/emailProvider"; import { CoingeckoArweaveToFiatOracle, ReadThroughArweaveToFiatOracle, @@ -115,4 +116,13 @@ export const axios = axiosPackage.create({ baseURL: localTestUrl, validateStatus: () => true, }); +export const emailProvider = new MandrillEmailProvider("test"); + export const testAddress = "-kYy3_LcYeKhtqNNXDN6xTQ7hW8S5EV0jgq_6j8a830"; // cspell:disable-line + +export function removeCatalogIdMap( + a: T +): Omit { + const { catalogId: _, ...allButCatalogId } = a; + return allButCatalogId; +} diff --git a/tests/postgres.int.spec.ts b/tests/postgres.int.test.ts similarity index 61% rename from tests/postgres.int.spec.ts rename to tests/postgres.int.test.ts index 68315b0..1d58c7f 100644 --- a/tests/postgres.int.spec.ts +++ b/tests/postgres.int.test.ts @@ -18,12 +18,19 @@ import { expect } from "chai"; import { tableNames } from "../src/database/dbConstants"; import { + AuditLogDBResult, + BalanceReservationDBResult, ChargebackReceiptDBResult, + PaymentAdjustmentDBResult, PaymentReceiptDBResult, + SingleUseCodePaymentCatalogDBResult, TopUpQuoteDBResult, + UploadAdjustment, + UploadAdjustmentDBInsert, UserDBResult, } from "../src/database/dbTypes"; import { PostgresDatabase } from "../src/database/postgres"; +import { FinalPrice, NetworkPrice } from "../src/pricing/price"; import { Winston } from "../src/types/winston"; import { DbTestHelper } from "./dbTestHelper"; import { stubTxId1 } from "./helpers/stubs"; @@ -33,6 +40,22 @@ describe("PostgresDatabase class", () => { const db = new PostgresDatabase(); const dbTestHelper = new DbTestHelper(db); + const postgresTestPromoCode = "postgresTestPromoCode"; + const postgresTestPromoCodeCatalogId = "postgresTestPromoCodeCatalogId"; + + before(async () => { + await db["writer"]( + tableNames.singleUseCodePaymentAdjustmentCatalog + ).insert({ + code_value: postgresTestPromoCode, + adjustment_exclusivity: "exclusive", + adjustment_name: "Postgres Test Promo Code", + catalog_id: postgresTestPromoCodeCatalogId, + operator: "multiply", + operator_magnitude: "0.8", + }); + }); + describe("createTopUpQuote method", () => { const quoteExpirationDate = new Date( "2023-03-23 12:34:56.789Z" @@ -41,8 +64,20 @@ describe("PostgresDatabase class", () => { before(async () => { await db.createTopUpQuote({ paymentAmount: 100, + quotedPaymentAmount: 150, currencyType: "usd", destinationAddress: "XYZ", + adjustments: [ + { + adjustmentAmount: -50, + catalogId: "uuid", + currencyType: "usd", + name: "best adjustment", + operator: "add", + operatorMagnitude: -50, + description: "fifty cents off", + }, + ], destinationAddressType: "arweave", quoteExpirationDate, paymentProvider: "stripe", @@ -52,13 +87,14 @@ describe("PostgresDatabase class", () => { }); it("creates the expected top up quote in the database", async () => { - const topUpQuote = await db["knexWriter"]( + const topUpQuote = await db["writer"]( tableNames.topUpQuote ).where({ top_up_quote_id: "Unique Identifier" }); expect(topUpQuote.length).to.equal(1); const { payment_amount, + quoted_payment_amount, currency_type, destination_address, destination_address_type, @@ -70,6 +106,7 @@ describe("PostgresDatabase class", () => { } = topUpQuote[0]; expect(payment_amount).to.equal("100"); + expect(quoted_payment_amount).to.equal("150"); expect(currency_type).to.equal("usd"); expect(destination_address).to.equal("XYZ"); expect(destination_address_type).to.equal("arweave"); @@ -81,6 +118,35 @@ describe("PostgresDatabase class", () => { expect(top_up_quote_id).to.equal("Unique Identifier"); expect(winston_credit_amount).to.equal("500"); }); + + it("creates the expected payment adjustment in the database", async () => { + const paymentAdjustments = await db["writer"]( + tableNames.paymentAdjustment + ).where({ + top_up_quote_id: "Unique Identifier", + }); + expect(paymentAdjustments.length).to.equal(1); + + const { + adjusted_currency_type, + adjusted_payment_amount, + adjustment_date, + adjustment_id, + adjustment_index, + catalog_id, + user_address, + top_up_quote_id, + } = paymentAdjustments[0]; + + expect(adjusted_payment_amount).to.equal("-50"); + expect(catalog_id).to.equal("uuid"); + expect(adjusted_currency_type).to.equal("usd"); + expect(user_address).to.equal("XYZ"); + expect(adjustment_index).to.equal(0); + expect(adjustment_date).to.exist; + expect(top_up_quote_id).to.equal("Unique Identifier"); + expect(adjustment_id).to.be.a("number"); + }); }); describe("getTopUpQuote method", () => { @@ -159,7 +225,7 @@ describe("PostgresDatabase class", () => { }); it("creates the expected payment_receipt in the database entity", async () => { - const paymentReceipt = await db["knexWriter"]( + const paymentReceipt = await db["writer"]( tableNames.paymentReceipt ).where({ payment_receipt_id: "Unique Identifier" }); expect(paymentReceipt.length).to.equal(1); @@ -188,7 +254,7 @@ describe("PostgresDatabase class", () => { }); it("creates the expected new user when an existing user address cannot be found", async () => { - const user = await db["knexWriter"](tableNames.user).where({ + const user = await db["writer"](tableNames.user).where({ user_address: newUserAddress, }); expect(user.length).to.equal(1); @@ -207,9 +273,7 @@ describe("PostgresDatabase class", () => { }); it("increments existing user's balance as expected", async () => { - const oldUser = await db["knexWriter"]( - tableNames.user - ).where({ + const oldUser = await db["writer"](tableNames.user).where({ user_address: oldUserAddress, }); expect(oldUser.length).to.equal(1); @@ -220,7 +284,7 @@ describe("PostgresDatabase class", () => { }); it("deletes the top_up_quotes as expected", async () => { - const topUpQuoteDbResults = await db["knexWriter"]( + const topUpQuoteDbResults = await db["writer"]( tableNames.topUpQuote ); const topUpIds = topUpQuoteDbResults.map((r) => r.top_up_quote_id); @@ -251,7 +315,7 @@ describe("PostgresDatabase class", () => { expect( ( - await db["knexWriter"](tableNames.paymentReceipt).where({ + await db["writer"](tableNames.paymentReceipt).where({ payment_receipt_id: "This is fine", }) ).length @@ -283,7 +347,7 @@ describe("PostgresDatabase class", () => { expect( ( - await db["knexWriter"](tableNames.paymentReceipt).where({ + await db["writer"](tableNames.paymentReceipt).where({ payment_receipt_id: "This is a string", }) ).length @@ -304,12 +368,82 @@ describe("PostgresDatabase class", () => { expect( ( - await db["knexWriter"](tableNames.paymentReceipt).where({ + await db["writer"](tableNames.paymentReceipt).where({ payment_receipt_id: "This is fine", }) ).length ).to.equal(0); }); + + it("errors as expected when 20% off promo code payment adjustment is no longer eligible", async () => { + const userAddress = "this promo code User Address"; + const firstTopUpQuoteId = "First Top Up Quote ID"; + const secondTopUpQuoteId = "Second Top Up Quote ID"; + + const promoCodeEventCatalogId = await db[ + "reader" + ]( + tableNames.singleUseCodePaymentAdjustmentCatalog + ) + .where({ code_value: postgresTestPromoCode }) + .first() + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + .then((r) => r!.catalog_id); + + await dbTestHelper.insertStubTopUpQuote({ + destination_address: userAddress, + top_up_quote_id: firstTopUpQuoteId, + }); + await dbTestHelper.insertStubTopUpQuote({ + destination_address: userAddress, + top_up_quote_id: secondTopUpQuoteId, + }); + await dbTestHelper.insertStubPaymentAdjustment({ + top_up_quote_id: firstTopUpQuoteId, + user_address: userAddress, + catalog_id: promoCodeEventCatalogId, + }); + await dbTestHelper.insertStubPaymentAdjustment({ + top_up_quote_id: secondTopUpQuoteId, + user_address: userAddress, + catalog_id: promoCodeEventCatalogId, + }); + + await db.createPaymentReceipt({ + currencyType: "usd", + paymentAmount: 100, + topUpQuoteId: firstTopUpQuoteId, + paymentReceiptId: "Unique Identifier promo code", + }); + + expect( + ( + await db["reader"](tableNames.paymentReceipt).where({ + payment_receipt_id: "Unique Identifier promo code", + }) + ).length + ).to.equal(1); + + await expectAsyncErrorThrow({ + promiseToError: db.createPaymentReceipt({ + currencyType: "usd", + paymentAmount: 100, + topUpQuoteId: secondTopUpQuoteId, + paymentReceiptId: "Unique Identifier second promo code ", + }), + errorMessage: + "The user 'this promo code User Address' is ineligible for the promo code 'postgresTestPromoCode'", + errorType: "UserIneligibleForPromoCode", + }); + + expect( + ( + await db["reader"](tableNames.paymentReceipt).where({ + payment_receipt_id: "Unique Identifier second promo code ", + }) + ).length + ).to.equal(0); + }); }); describe("getPaymentReceipt method", () => { @@ -368,9 +502,9 @@ describe("PostgresDatabase class", () => { }); it("creates the expected chargeback receipt in the database", async () => { - const chargebackReceipt = await db[ - "knexWriter" - ](tableNames.chargebackReceipt).where({ + const chargebackReceipt = await db["writer"]( + tableNames.chargebackReceipt + ).where({ chargeback_receipt_id: "A great Unique Identifier", }); expect(chargebackReceipt.length).to.equal(1); @@ -402,7 +536,7 @@ describe("PostgresDatabase class", () => { it("deletes the payment_receipt entity", async () => { const paymentReceiptDbResults = await db[ - "knexWriter" + "writer" ](tableNames.paymentReceipt).where({ payment_receipt_id: naughtyPaymentId, }); @@ -410,9 +544,7 @@ describe("PostgresDatabase class", () => { }); it("decrements user's balance as expected", async () => { - const oldUser = await db["knexWriter"]( - tableNames.user - ).where({ + const oldUser = await db["writer"](tableNames.user).where({ user_address: naughtyUserAddress, }); expect(oldUser.length).to.equal(1); @@ -434,7 +566,7 @@ describe("PostgresDatabase class", () => { expect( ( - await db["knexWriter"](tableNames.chargebackReceipt).where({ + await db["writer"](tableNames.chargebackReceipt).where({ chargeback_receipt_id: "Great value", }) ).length @@ -458,7 +590,7 @@ describe("PostgresDatabase class", () => { winston_credit_amount: disputedWinstonAmount, }); - const negativeBalanceUserBefore = await db["knexWriter"]( + const negativeBalanceUserBefore = await db["writer"]( tableNames.user ).where({ user_address: underfundedUserAddress, @@ -474,7 +606,7 @@ describe("PostgresDatabase class", () => { chargebackReason: "Stripe Dispute Webhook Event", }); - const negativeBalanceAfter = await db["knexWriter"]( + const negativeBalanceAfter = await db["writer"]( tableNames.user ).where({ user_address: underfundedUserAddress, @@ -492,7 +624,7 @@ describe("PostgresDatabase class", () => { expect( ( - await db["knexWriter"](tableNames.chargebackReceipt).where({ + await db["writer"](tableNames.chargebackReceipt).where({ chargeback_receipt_id: chargebackReceiptId, }) ).length @@ -654,20 +786,89 @@ describe("PostgresDatabase class", () => { }); it("reserves the balance as expected when winston balance is available", async () => { - await db.reserveBalance(richAddress, new Winston(500), stubTxId1); + const adjustments: UploadAdjustment[] = [ + { + adjustmentAmount: new Winston(-500), + name: "Best Adjustment Ever", + operator: "add", + operatorMagnitude: -500, + description: "we rebate 500 winc on your upload :)", + catalogId: "Stub Catalog ID", + }, + { + adjustmentAmount: new Winston(-2345678), + name: "Another good Adjustment Ever", + operator: "multiply", + operatorMagnitude: 0.5, + description: "we subsidize 50% of your upload 👍", + catalogId: "Another stub catalog id", + }, + ]; + + await db.reserveBalance({ + userAddress: richAddress, + reservedWincAmount: new FinalPrice(new Winston(500)), + networkWincAmount: new NetworkPrice(new Winston(500)), + dataItemId: stubTxId1, + adjustments, + }); const richUser = await db.getUser(richAddress); - expect(+richUser.winstonCreditBalance).to.equal(99_999_999_500); + + const balanceReservationDbResult = await db[ + "writer" + ](tableNames.balanceReservation).where({ + data_item_id: stubTxId1, + }); + + expect(balanceReservationDbResult.length).to.equal(1); + expect(balanceReservationDbResult[0].user_address).to.equal(richAddress); + expect(balanceReservationDbResult[0].reserved_winc_amount).to.equal( + "500" + ); + expect(balanceReservationDbResult[0].reserved_date).to.exist; + expect(typeof balanceReservationDbResult[0].reservation_id).to.equal( + "string" + ); + expect(balanceReservationDbResult[0].data_item_id).to.equal(stubTxId1); + + const adjustmentDbResult = await db["writer"]( + tableNames.uploadAdjustment + ).where({ + reservation_id: balanceReservationDbResult[0].reservation_id, + }); + expect(adjustmentDbResult.length).to.equal(2); + + expect(adjustmentDbResult[0].adjusted_winc_amount).to.equal("-500"); + expect(adjustmentDbResult[0].adjustment_index).to.equal(0); + + expect(adjustmentDbResult[1].adjusted_winc_amount).to.equal("-2345678"); + expect(adjustmentDbResult[1].adjustment_index).to.equal(1); + + const auditLogDbResult = await db["writer"]( + tableNames.auditLog + ).where({ + change_id: stubTxId1, + }); + + expect(auditLogDbResult.length).to.equal(1); + expect(auditLogDbResult[0].user_address).to.equal(richAddress); + expect(auditLogDbResult[0].winston_credit_amount).to.equal("-500"); + expect(auditLogDbResult[0].change_id).to.equal(stubTxId1); + expect(typeof auditLogDbResult[0].audit_id).to.equal("number"); + expect(auditLogDbResult[0].change_reason).to.equal("upload"); }); it("throws an error as expected when winston balance is not available", async () => { await expectAsyncErrorThrow({ - promiseToError: db.reserveBalance( - poorAddress, - new Winston(200), - stubTxId1 - ), + promiseToError: db.reserveBalance({ + userAddress: poorAddress, + reservedWincAmount: new FinalPrice(new Winston(200)), + networkWincAmount: new NetworkPrice(new Winston(200)), + adjustments: [], + dataItemId: stubTxId1, + }), errorType: "InsufficientBalance", errorMessage: `Insufficient balance for '${poorAddress}'`, }); @@ -678,11 +879,13 @@ describe("PostgresDatabase class", () => { it("throws a warning as expected when user cannot be found", async () => { await expectAsyncErrorThrow({ - promiseToError: db.reserveBalance( - "Non Existent Address", - new Winston(1337), - stubTxId1 - ), + promiseToError: db.reserveBalance({ + userAddress: "Non Existent Address", + reservedWincAmount: new FinalPrice(new Winston(200)), + networkWincAmount: new NetworkPrice(new Winston(200)), + adjustments: [], + dataItemId: stubTxId1, + }), errorType: "UserNotFoundWarning", errorMessage: "No user found in database with address 'Non Existent Address'", @@ -721,4 +924,149 @@ describe("PostgresDatabase class", () => { }); }); }); + + describe("getSingleUsePromoCodeAdjustments", () => { + it("returns the expected adjustment for a user and 20% off promo code", async () => { + const userAddress = "userAddress"; + + const adjustmentDbResult = await db.getSingleUsePromoCodeAdjustments( + [postgresTestPromoCode], + userAddress + ); + expect(adjustmentDbResult.length).to.equal(1); + + const { + catalogId, + codeValue, + exclusivity, + name, + operator, + operatorMagnitude, + priority, + startDate, + description, + endDate, + } = adjustmentDbResult[0]; + + expect(catalogId).to.equal(postgresTestPromoCodeCatalogId); + expect(codeValue).to.be.equal(postgresTestPromoCode); + expect(exclusivity).to.be.equal("exclusive"); + expect(name).to.be.equal("Postgres Test Promo Code"); + expect(operator).to.be.equal("multiply"); + expect(operatorMagnitude).to.be.equal(0.8); + expect(priority).to.be.equal(500); + expect(startDate).to.be.a("date"); + expect(description).to.be.equal(""); + expect(endDate).to.be.null; + }); + + describe("test pilot referral code", () => { + const pilotReferralPromoCode = "pilotReferralPromoCode"; + const pilotReferralPromoCodeCatalogId = "pilotReferralPromoCodeCatalogId"; + + before(async () => { + await db["writer"]( + tableNames.singleUseCodePaymentAdjustmentCatalog + ).insert({ + code_value: pilotReferralPromoCode, + adjustment_exclusivity: "exclusive", + adjustment_name: "Pilot Referral Promo Code", + catalog_id: pilotReferralPromoCodeCatalogId, + target_user_group: "new", + max_uses: 10, + minimum_payment_amount: 5000, + operator: "add", + operator_magnitude: "-500", + adjustment_start_date: "2023-09-20T16:47:37.660Z", // in the past + }); + }); + + it("returns the expected adjustment when un-used", async () => { + const userAddress = "userAddress"; + + const adjustmentDbResult = await db.getSingleUsePromoCodeAdjustments( + [pilotReferralPromoCode], + userAddress + ); + expect(adjustmentDbResult.length).to.equal(1); + + const { + catalogId, + codeValue, + exclusivity, + name, + operator, + operatorMagnitude, + priority, + startDate, + description, + endDate, + maxUses, + minimumPaymentAmount, + targetUserGroup, + } = adjustmentDbResult[0]; + + expect(catalogId).to.equal(pilotReferralPromoCodeCatalogId); + expect(codeValue).to.be.equal(pilotReferralPromoCode); + expect(exclusivity).to.be.equal("exclusive"); + expect(name).to.be.equal("Pilot Referral Promo Code"); + expect(operator).to.be.equal("add"); + expect(operatorMagnitude).to.be.equal(-500); + expect(maxUses).to.be.equal(10); + expect(minimumPaymentAmount).to.be.equal(5000); + expect(targetUserGroup).to.be.equal("new"); + expect(priority).to.be.equal(500); + expect(startDate).to.be.a("date"); + expect(description).to.be.equal(""); + expect(endDate).to.be.null; + }); + + it("errors as expected when used beyond max uses", async () => { + for (let i = 0; i < 10; i++) { + // Insert 10 stub payment adjustments with catalog id + await dbTestHelper.insertStubPaymentAdjustment({ + catalog_id: pilotReferralPromoCodeCatalogId, + }); + } + + await expectAsyncErrorThrow({ + promiseToError: db.getSingleUsePromoCodeAdjustments( + [pilotReferralPromoCode], + "userAddress" + ), + errorMessage: `The promo code '${pilotReferralPromoCode}' has already been used the maximum number of times (10)`, + errorType: "PromoCodeExceedsMaxUses", + }); + }); + }); + + describe("when promo code has been used", () => { + const usedPromoCodeUserAddress = "usedPromoCodeUserAddress"; + const usedPromoCodeTopUpQuoteId = "usedPromoCodeTopUpQuoteId"; + + before(async () => { + // Presence of Payment Receipt and Payment Adjustment indicates that the promo code has been used + await dbTestHelper.insertStubPaymentReceipt({ + destination_address: usedPromoCodeUserAddress, + top_up_quote_id: usedPromoCodeTopUpQuoteId, + }); + await dbTestHelper.insertStubPaymentAdjustment({ + catalog_id: postgresTestPromoCodeCatalogId, + top_up_quote_id: usedPromoCodeTopUpQuoteId, + user_address: usedPromoCodeUserAddress, + }); + }); + + it("throws an error for a user when already used 20% off promo code", async () => { + await expectAsyncErrorThrow({ + promiseToError: db.getSingleUsePromoCodeAdjustments( + [postgresTestPromoCode], + usedPromoCodeUserAddress + ), + errorMessage: `The user 'usedPromoCodeUserAddress' is ineligible for the promo code '${postgresTestPromoCode}'`, + errorType: "UserIneligibleForPromoCode", + }); + }); + }); + }); }); diff --git a/tests/router.int.test.ts b/tests/router.int.test.ts index c17bb44..2378873 100644 --- a/tests/router.int.test.ts +++ b/tests/router.int.test.ts @@ -15,10 +15,11 @@ * along with this program. If not, see . */ import Arweave from "arweave/node/common"; +import axiosPackage from "axios"; import { expect } from "chai"; import { Server } from "http"; import { sign } from "jsonwebtoken"; -import { spy, stub } from "sinon"; +import { spy, stub, useFakeTimers } from "sinon"; import Stripe from "stripe"; import { @@ -29,7 +30,14 @@ import { import { tableNames } from "../src/database/dbConstants"; import { ChargebackReceiptDBResult, + PaymentReceiptDBInsert, PaymentReceiptDBResult, + RedeemedGiftDBResult, + SingleUseCodePaymentCatalogDBResult, + TopUpQuote, + TopUpQuoteDBResult, + UnredeemedGiftDBInsert, + UnredeemedGiftDBResult, UserDBResult, } from "../src/database/dbTypes.js"; import logger from "../src/logger"; @@ -37,29 +45,32 @@ import { CoingeckoArweaveToFiatOracle, ReadThroughArweaveToFiatOracle, } from "../src/pricing/oracles/arweaveToFiatOracle"; +import { FinalPrice, NetworkPrice } from "../src/pricing/price"; import { TurboPricingService } from "../src/pricing/pricing"; import { createServer } from "../src/server"; import { supportedPaymentCurrencyTypes } from "../src/types/supportedCurrencies"; import { Winston } from "../src/types/winston"; +import { arweaveRSAModulusToAddress } from "../src/utils/jwkUtils"; import { signedRequestHeadersFromJwk } from "../tests/helpers/signData"; +import { oneHourAgo, oneHourFromNow } from "./dbTestHelper"; import { chargeDisputeStub, checkoutSessionStub, checkoutSessionSuccessStub, - expectedAdjustments, expectedArPrices, - expectedRates, paymentIntentStub, stripeResponseStub, stripeStubEvent, stubTxId1, + stubTxId2, } from "./helpers/stubs"; import { assertExpectedHeadersWithContentLength } from "./helpers/testExpectations"; import { axios, - coinGeckoAxios, coinGeckoOracle, dbTestHelper, + emailProvider, + localTestUrl, paymentDatabase, pricingService, stripe, @@ -75,8 +86,37 @@ describe("Router tests", () => { logger.info("Server closed!"); } + const routerTestPromoCode = "routerTestPromoCode"; + const routerTestPromoCodeCatalogId = "routerTestPromoCodeCatalogId"; + + beforeEach(() => { + stub(coinGeckoOracle, "getFiatPricesForOneAR").resolves( + expectedArPrices.arweave + ); + }); + before(async () => { - server = await createServer({ pricingService, paymentDatabase, stripe }); + await dbTestHelper.insertStubUser({ + user_address: testAddress, + winston_credit_balance: "5000000", + }); + + server = await createServer({ + pricingService, + paymentDatabase, + stripe, + emailProvider, + }); + await paymentDatabase["writer"]( + tableNames.singleUseCodePaymentAdjustmentCatalog + ).insert({ + code_value: routerTestPromoCode, + adjustment_exclusivity: "exclusive", + adjustment_name: "Router Test Promo Code", + catalog_id: routerTestPromoCodeCatalogId, + operator: "multiply", + operator_magnitude: "0.8", + }); }); after(() => { @@ -97,7 +137,8 @@ describe("Router tests", () => { it("GET /price/bytes", async () => { const wincTotal = new Winston("1234567890"); stub(pricingService, "getWCForBytes").resolves({ - winc: wincTotal, + finalPrice: new FinalPrice(wincTotal), + networkPrice: new NetworkPrice(wincTotal), adjustments: [], }); @@ -110,6 +151,40 @@ describe("Router tests", () => { expect(+new Winston(data.winc)).to.equal(1234567890); }); + it("GET /price/arweave/:bytes", async () => { + const wincTotal = new Winston("1234567890"); + stub(pricingService, "getWCForBytes").resolves({ + finalPrice: new FinalPrice(wincTotal), + networkPrice: new NetworkPrice(wincTotal), + adjustments: [], + }); + + const { status, statusText, data } = await axios.get(`/price/arweave/1024`); + expect(status).to.equal(200); + expect(statusText).to.equal("OK"); + + expect(+new Winston(data)).to.equal(1234567890); + }); + + it("GET /price/arweave/:bytes returns 400 for invalid byte count", async () => { + const { status, statusText, data } = await axios.get( + `/price/arweave/-54.2` + ); + expect(status).to.equal(400); + expect(statusText).to.equal("Bad Request"); + expect(data).to.equal("Invalid byte count"); + }); + + it("GET /price/arweave/:bytes returns 502 if bytes pricing oracle fails to get a price", async () => { + stub(pricingService, "getWCForBytes").throws(Error("Serious failure")); + const { status, statusText, data } = await axios.get( + `/price/arweave/1321321` + ); + expect(status).to.equal(502); + expect(data).to.equal("Pricing Oracle Unavailable"); + expect(statusText).to.equal("Bad Gateway"); + }); + it("GET /price/bytes returns 400 for bytes > max safe integer", async () => { const { status, statusText, data } = await axios.get( `/v1/price/bytes/1024000000000000000000000000000000000000000000` @@ -139,14 +214,7 @@ describe("Router tests", () => { }); it("GET /price/:currency/:value returns 502 if fiat pricing oracle response is unexpected", async () => { - stub(coinGeckoAxios, "get").resolves({ - data: { - arweave: { - weird: "types", - from: ["c", 0, "in", "ge", "ck", 0], - }, - }, - }); + stub(pricingService, "getWCForPayment").throws(); const { data, status, statusText } = await axios.get(`/v1/price/usd/5000`); expect(status).to.equal(502); @@ -155,7 +223,8 @@ describe("Router tests", () => { }); it("GET /rates returns 502 if unable to fetch prices", async () => { - stub(coinGeckoOracle, "getFiatPricesForOneAR").throws(); + stub(pricingService, "getWCForBytes").throws(Error("Serious failure")); + const { status, statusText } = await axios.get(`/v1/rates`); expect(status).to.equal(502); @@ -163,18 +232,98 @@ describe("Router tests", () => { }); it("GET /rates returns the correct response", async () => { - stub(coinGeckoOracle, "getFiatPricesForOneAR").resolves( - expectedArPrices.arweave + const fakeDateBeforeSubsidyAndInfraFee = new Date( + "2021-01-01T00:00:00.000Z" + ); + const clock = useFakeTimers(fakeDateBeforeSubsidyAndInfraFee.getTime()); + + const { data, status, statusText } = await axios.get(`/v1/rates`); + + expect(status).to.equal(200); + expect(statusText).to.equal("OK"); + + expect(data).to.deep.equal({ + // No Infra Fee + fiat: { + aud: 8.888074843239, + brl: 29.787061636803, + cad: 8.055890229538, + eur: 5.48212338304, + gbp: 4.770047888842, + hkd: 47.177146296308, + inr: 493.502634370348, + jpy: 809.295247212831, + sgd: 7.987256446965, + usd: 6.022614420805, + }, + // No Subsidy + winc: "857922282166", + adjustments: [], + }); + clock.restore(); + }); + + it("GET /rates during twenty percent infra fee event returns the expected result", async () => { + const fakeDateDuringTwentyPctInfraFee = new Date( + "2023-01-02T00:00:00.000Z" ); + const clock = useFakeTimers(fakeDateDuringTwentyPctInfraFee.getTime()); + const { data, status, statusText } = await axios.get(`/v1/rates`); expect(status).to.equal(200); expect(statusText).to.equal("OK"); expect(data).to.deep.equal({ - ...expectedRates, - adjustments: expectedAdjustments, + // No Subsidy + fiat: { + aud: 11.110093554049, + brl: 37.233827046004, + cad: 10.069862786923, + eur: 6.8526542288, + gbp: 5.962559861053, + hkd: 58.971432870385, + inr: 616.878292962935, + jpy: 1011.619059016038, + sgd: 9.984070558706, + usd: 7.528268026006, + }, + winc: "857922282166", + adjustments: [], + }); + clock.restore(); + }); + + it("GET /rates during twenty three point four percent infra fee event and Sep - Oct Subsidy Event returns the expected result", async () => { + const fakeDateDuringTwentyThreeFourPctInfraFeeAndSepOctSubsidyEvent = + new Date("2023-09-25T00:00:00.000Z"); + const clock = useFakeTimers( + fakeDateDuringTwentyThreeFourPctInfraFeeAndSepOctSubsidyEvent.getTime() + ); + + const { data, status, statusText } = await axios.get(`/v1/rates`); + + expect(status).to.equal(200); + expect(statusText).to.equal("OK"); + + expect(data.fiat).to.deep.equal({ + // 23.4% Infra Fee applied + aud: 6.381776976212, + brl: 21.387576893252, + cad: 5.78425538674, + eur: 3.936250470849, + gbp: 3.424969110785, + hkd: 33.873930108293, + inr: 354.34262258945, + jpy: 581.086665752968, + sgd: 5.73497525565, + usd: 4.324331503186, }); + + // 45% Subsidy Event applied + expect(data.winc).to.equal("471857255191"); + expect(data.adjustments[0].adjustmentAmount).to.equal("-386065026975"); + clock.restore(); }); it("GET /rates/:currency returns 404 for non supported currency", async () => { @@ -192,9 +341,6 @@ describe("Router tests", () => { }); it("GET /rates/:currency returns the correct response for supported currency", async () => { - stub(coinGeckoOracle, "getFiatPricesForOneAR").resolves( - expectedArPrices.arweave - ); const { data, status, statusText } = await axios.get(`/v1/rates/usd`); expect(status).to.equal(200); @@ -207,24 +353,252 @@ describe("Router tests", () => { }); it("GET /price/:currency/:value", async () => { - stub(coinGeckoOracle, "getFiatPricesForOneAR").resolves( - expectedArPrices.arweave - ); const { status, statusText, data } = await axios.get(`/v1/price/USD/100`); expect(status).to.equal(200); expect(statusText).to.equal("OK"); - expect(+new Winston(data.winc)).to.equal(109686609687); + const { + winc, + actualPaymentAmount, + quotedPaymentAmount, + adjustments, + fees, + } = data; + + expect(+new Winston(winc)).to.equal(109686609687); + expect(quotedPaymentAmount).to.equal(100); + expect(actualPaymentAmount).to.equal(100); + expect(fees).to.deep.equal([ + { + adjustmentAmount: -23, + currencyType: "usd", + description: + "Inclusive usage fee on all payments to cover infrastructure costs and payment provider fees.", + name: "Turbo Infrastructure Fee", + operator: "multiply", + operatorMagnitude: 0.766, + }, + ]); + expect(adjustments).to.deep.equal([]); + }); + + it("GET /price/:currency/:value with a 20% off promoCode in query params returns expected result", async () => { + const { status, statusText, data } = await axiosPackage + .create({ + baseURL: localTestUrl, + validateStatus: () => true, + }) + .get(`/v1/price/USD/123?promoCode=${routerTestPromoCode}`, { + headers: await signedRequestHeadersFromJwk(testWallet, "123"), + }); + + expect(status).to.equal(200); + expect(statusText).to.equal("OK"); + + const { + winc, + actualPaymentAmount, + quotedPaymentAmount, + adjustments, + fees, + } = data; + + expect(+new Winston(winc)).to.equal(133903133903); + expect(quotedPaymentAmount).to.equal(123); + expect(actualPaymentAmount).to.equal(98); + expect(adjustments).to.deep.equal([ + { + adjustmentAmount: -25, + currencyType: "usd", + description: "", + name: "Router Test Promo Code", + operator: "multiply", + operatorMagnitude: 0.8, + promoCode: routerTestPromoCode, + }, + ]); + expect(fees).to.deep.equal([ + { + adjustmentAmount: -29, + currencyType: "usd", + description: + "Inclusive usage fee on all payments to cover infrastructure costs and payment provider fees.", + name: "Turbo Infrastructure Fee", + operator: "multiply", + operatorMagnitude: 0.766, + }, + ]); + }); + + it("GET /price/:currency/:value with a 20% off promoCode and destinationAddress in query params returns expected result", async () => { + const destinationAddress = "43CharactersABCDEFGHIJKLMNOPQRSTUVWXYZ12345"; + const { status, statusText, data } = await axiosPackage + .create({ + baseURL: localTestUrl, + validateStatus: () => true, + }) + .get( + `/v1/price/USD/123?promoCode=${routerTestPromoCode}&destinationAddress=${destinationAddress}` + ); + + expect(status).to.equal(200); + expect(statusText).to.equal("OK"); + + const { + winc, + actualPaymentAmount, + quotedPaymentAmount, + adjustments, + fees, + } = data; + + expect(+new Winston(winc)).to.equal(133903133903); + expect(quotedPaymentAmount).to.equal(123); + expect(actualPaymentAmount).to.equal(98); + expect(adjustments).to.deep.equal([ + { + adjustmentAmount: -25, + currencyType: "usd", + description: "", + name: "Router Test Promo Code", + operator: "multiply", + operatorMagnitude: 0.8, + promoCode: routerTestPromoCode, + }, + ]); + expect(fees).to.deep.equal([ + { + adjustmentAmount: -29, + currencyType: "usd", + description: + "Inclusive usage fee on all payments to cover infrastructure costs and payment provider fees.", + name: "Turbo Infrastructure Fee", + operator: "multiply", + operatorMagnitude: 0.766, + }, + ]); + }); + + it("GET /price/:currency/:value with duplicate 20% off promoCodes in query params returns expected result", async () => { + const { status, statusText, data } = await axiosPackage + .create({ + baseURL: localTestUrl, + validateStatus: () => true, + }) + .get( + `/v1/price/USD/1234?promoCode=${routerTestPromoCode}&promoCode=${routerTestPromoCode}`, + { + headers: await signedRequestHeadersFromJwk(testWallet, "123"), + } + ); + + expect(status).to.equal(200); + expect(statusText).to.equal("OK"); + + const { + winc, + actualPaymentAmount, + quotedPaymentAmount, + adjustments, + fees, + } = data; + + expect(+new Winston(winc)).to.equal(1346153846154); + expect(quotedPaymentAmount).to.equal(1234); + expect(actualPaymentAmount).to.equal(987); + expect(adjustments).to.deep.equal([ + { + adjustmentAmount: -247, + currencyType: "usd", + description: "", + name: "Router Test Promo Code", + operator: "multiply", + operatorMagnitude: 0.8, + promoCode: "routerTestPromoCode", + }, + ]); + expect(fees).to.deep.equal([ + { + adjustmentAmount: -289, + currencyType: "usd", + description: + "Inclusive usage fee on all payments to cover infrastructure costs and payment provider fees.", + name: "Turbo Infrastructure Fee", + operator: "multiply", + operatorMagnitude: 0.766, + }, + ]); + }); + + it("GET /price/:currency/:value with INVALID promoCode in query params returns a 400", async () => { + const { status, statusText, data } = await axiosPackage + .create({ + baseURL: localTestUrl, + validateStatus: () => true, + }) + .get(`/v1/price/USD/100?promoCode=fakeCodeLOL`, { + headers: await signedRequestHeadersFromJwk(testWallet, "123"), + }); + + expect(data).to.equal("No promo code found with code 'fakeCodeLOL'"); + expect(status).to.equal(400); + expect(statusText).to.equal("Bad Request"); + }); + + it("GET /price/:currency/:value with INELIGIBLE promoCode in query params returns a 400", async () => { + const jwk = await Arweave.crypto.generateJWK(); + const userAddress = arweaveRSAModulusToAddress(jwk.n); + + await dbTestHelper.insertStubPaymentReceipt({ + payment_receipt_id: "unique id promo ineligible price fiat", + top_up_quote_id: "used promo code id", + destination_address: userAddress, + }); + await dbTestHelper.insertStubPaymentAdjustment({ + catalog_id: routerTestPromoCodeCatalogId, + top_up_quote_id: "used promo code id", + user_address: userAddress, + }); + + const { status, statusText, data } = await axiosPackage + .create({ + baseURL: localTestUrl, + validateStatus: () => true, + }) + // This wallet just used this code above... So we should now fail + .get(`/v1/price/USD/100?promoCode=${routerTestPromoCode}`, { + headers: await signedRequestHeadersFromJwk(jwk, "123"), + }); + + expect(data).to.equal( + `The user '${userAddress}' is ineligible for the promo code '${routerTestPromoCode}'` + ); + expect(status).to.equal(400); + expect(statusText).to.equal("Bad Request"); + }); + + it("GET /price/:currency/:value with promoCode in query params but an unauthenticated request and lacking a destinationAddress returns a 400", async () => { + const { status, statusText, data } = await axiosPackage + .create({ + baseURL: localTestUrl, + validateStatus: () => true, + }) + .get(`/v1/price/USD/100?promoCode=${routerTestPromoCode}`); + + expect(data).to.equal( + "Promo codes must be applied to a specific `destinationAddress` or to the request signer" + ); + expect(status).to.equal(400); + expect(statusText).to.equal("Bad Request"); }); it("GET /price/:currency/:value returns 400 for invalid currency", async () => { const { data, status, statusText } = await axios.get( - `/v1/price/RandomCurrency/100` + `/v1/price/Random-Currency/100` ); expect(data).to.equal( - // cspell:disable - "The currency type 'randomcurrency' is currently not supported by this API!" // cspell:enable + "The currency type 'random-currency' is currently not supported by this API!" ); expect(status).to.equal(400); expect(statusText).to.equal("Bad Request"); @@ -248,13 +622,6 @@ describe("Router tests", () => { expect(data).to.equal("Fiat Oracle Unavailable"); }); - before(async () => { - await dbTestHelper.insertStubUser({ - user_address: testAddress, - winston_credit_balance: "5000000", - }); - }); - it("GET /balance returns 200 for correct signature", async () => { const { status, statusText, data } = await axios.get(`/v1/balance`, { headers: await signedRequestHeadersFromJwk(testWallet, "123"), @@ -307,16 +674,118 @@ describe("Router tests", () => { expect(data).to.equal("Cloud Database Unavailable"); }); - it("GET /top-up/checkout-session returns 200 and correct response for correct signature", async () => { + it("GET /top-up/checkout-session with an email in query params returns the correct response", async () => { const amount = 1000; + const email = "test@example.inc"; const checkoutStub = stub(stripe.checkout.sessions, "create").resolves( stripeResponseStub({ ...checkoutSessionSuccessStub, amount_total: amount, }) ); - stub(coinGeckoOracle, "getFiatPricesForOneAR").resolves( - expectedArPrices.arweave + + const { status, statusText, data } = await axios.get( + `/v1/top-up/checkout-session/${email}/usd/${amount}?destinationAddressType=email&giftMessage=hello%20world` + ); + + expect(data).to.have.property("topUpQuote"); + expect(data).to.have.property("paymentSession"); + expect(status).to.equal(200); + expect(statusText).to.equal("OK"); + + const { paymentSession, topUpQuote, adjustments, fees } = data; + const { object, payment_method_types, amount_total, url } = paymentSession; + + expect(object).to.equal("checkout.session"); + expect(payment_method_types).to.deep.equal(["card"]); + expect(amount_total).to.equal(amount); + expect(url).to.be.a.string; + + const { + quotedPaymentAmount, + paymentAmount, + topUpQuoteId, + destinationAddress, + destinationAddressType, + quoteExpirationDate, + } = topUpQuote; + + expect(quotedPaymentAmount).to.equal(1000); + expect(paymentAmount).to.equal(1000); + expect(topUpQuoteId).to.be.a.string; + expect(destinationAddress).to.equal(email); + expect(destinationAddressType).to.equal("email"); + expect(quoteExpirationDate).to.be.a.string; + + expect(fees).to.deep.equal([ + { + adjustmentAmount: -234, + currencyType: "usd", + description: + "Inclusive usage fee on all payments to cover infrastructure costs and payment provider fees.", + name: "Turbo Infrastructure Fee", + operator: "multiply", + operatorMagnitude: 0.766, + }, + ]); + expect(adjustments).to.deep.equal([]); + + const dbResult = await paymentDatabase["writer"]( + tableNames.topUpQuote + ) + .where({ top_up_quote_id: topUpQuoteId }) + .first(); + + expect(dbResult).to.not.be.undefined; + const { + currency_type, + top_up_quote_id, + payment_provider, + quoted_payment_amount, + payment_amount, + destination_address, + destination_address_type, + winston_credit_amount, + quote_expiration_date, + quote_creation_date, + gift_message, + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + } = dbResult!; + + expect(currency_type).to.equal("usd"); + expect(top_up_quote_id).to.be.a.string; + expect(payment_provider).to.equal("stripe"); + expect(quoted_payment_amount).to.equal("1000"); + expect(payment_amount).to.equal("1000"); + expect(destination_address).to.equal(email); + expect(destination_address_type).to.equal("email"); + expect(winston_credit_amount).to.equal("1091168091168"); + expect(new Date(quote_expiration_date).toISOString()).to.equal( + quoteExpirationDate.toString() + ); + expect(quote_creation_date).to.be.a.string; + expect(gift_message).to.equal("hello world"); + + checkoutStub.restore(); + }); + + it("GET /top-up/checkout-session with an invalid destination address type returns 400 response", async () => { + const { status, statusText, data } = await axios.get( + `/v1/top-up/checkout-session/hello-test/usd/4231?destinationAddressType=notReal` + ); + + expect(status).to.equal(400); + expect(statusText).to.equal("Bad Request"); + expect(data).to.equal("Invalid destination address type: notReal"); + }); + + it("GET /top-up/checkout-session returns 200 and correct response for correct signature", async () => { + const amount = 1000; + const checkoutStub = stub(stripe.checkout.sessions, "create").resolves( + stripeResponseStub({ + ...checkoutSessionSuccessStub, + amount_total: amount, + }) ); const { status, statusText, data } = await axios.get( @@ -349,10 +818,6 @@ describe("Router tests", () => { }) ); - stub(coinGeckoOracle, "getFiatPricesForOneAR").resolves( - expectedArPrices.arweave - ); - const { status, statusText, data } = await axios.get( `/v1/top-up/payment-intent/${testAddress}/usd/${topUpAmount}` ); @@ -379,9 +844,172 @@ describe("Router tests", () => { expect(client_secret).to.be.a.string; expect(metadata.topUpQuoteId).to.be.a.string; expect(paymentStatus).to.equal("requires_payment_method"); + + const { + quotedPaymentAmount, + paymentAmount, + topUpQuoteId, + winstonCreditAmount, + }: TopUpQuote = data.topUpQuote; + + expect(quotedPaymentAmount).to.equal(1000); + expect(paymentAmount).to.equal(1000); + expect(topUpQuoteId).to.be.a.string; + expect(winstonCreditAmount).to.equal("1091168091168"); + + expect(data.fees).to.deep.equal([ + { + adjustmentAmount: -234, + currencyType: "usd", + description: + "Inclusive usage fee on all payments to cover infrastructure costs and payment provider fees.", + name: "Turbo Infrastructure Fee", + operator: "multiply", + operatorMagnitude: 0.766, + }, + ]); + expect(data.adjustments).to.deep.equal([]); + + paymentIntentStubSpy.restore(); + }); + + it("GET /top-up with promoCode in query params returns the expected result", async () => { + const topUpAmount = 1000; + const paymentIntentStubSpy = stub(stripe.paymentIntents, "create").resolves( + stripeResponseStub({ + ...paymentIntentStub({ + amount: 800, + status: "requires_payment_method", + }), + }) + ); + + const { status, statusText, data } = await axiosPackage + .create({ + baseURL: localTestUrl, + validateStatus: () => true, + }) + .get( + `/v1/top-up/payment-intent/${testAddress}/usd/${topUpAmount}?promoCode=${routerTestPromoCode}`, + { + headers: await signedRequestHeadersFromJwk(testWallet, "123"), + } + ); + + expect(data).to.have.property("topUpQuote"); + expect(data).to.have.property("paymentSession"); + expect(status).to.equal(200); + expect(statusText).to.equal("OK"); + + const { + object, + payment_method_types, + amount, + currency, + client_secret, + status: paymentStatus, + metadata, + } = data.paymentSession; + + const { + quotedPaymentAmount, + paymentAmount, + topUpQuoteId, + winstonCreditAmount, + }: TopUpQuote = data.topUpQuote; + + expect(object).to.equal("payment_intent"); + expect(payment_method_types).to.deep.equal(["card"]); + expect(amount).to.equal(paymentAmount); + expect(currency).to.equal("usd"); + expect(client_secret).to.be.a.string; + expect(metadata.topUpQuoteId).to.be.a.string; + expect(paymentStatus).to.equal("requires_payment_method"); + + expect(quotedPaymentAmount).to.equal(1000); + expect(paymentAmount).to.equal(800); + expect(topUpQuoteId).to.be.a.string; + expect(winstonCreditAmount).to.equal("1091168091168"); + + expect(data.adjustments).to.deep.equal([ + { + adjustmentAmount: -200, + currencyType: "usd", + description: "", + name: "Router Test Promo Code", + operator: "multiply", + operatorMagnitude: 0.8, + promoCode: "routerTestPromoCode", + }, + ]); + expect(data.fees).to.deep.equal([ + { + adjustmentAmount: -234, + currencyType: "usd", + description: + "Inclusive usage fee on all payments to cover infrastructure costs and payment provider fees.", + name: "Turbo Infrastructure Fee", + operator: "multiply", + operatorMagnitude: 0.766, + }, + ]); + paymentIntentStubSpy.restore(); }); + it("GET /top-up with INVALID promoCode in query params returns a 400", async () => { + const { status, statusText, data } = await axiosPackage + .create({ + baseURL: localTestUrl, + validateStatus: () => true, + }) + .get( + `/v1/top-up/payment-intent/${testAddress}/usd/1000?promoCode=fakeCodeLOL`, + { + headers: await signedRequestHeadersFromJwk(testWallet, "123"), + } + ); + + expect(data).to.equal("No promo code found with code 'fakeCodeLOL'"); + expect(status).to.equal(400); + expect(statusText).to.equal("Bad Request"); + }); + + it("GET /top-up with INELIGIBLE promoCode in query params returns a 400", async () => { + const jwk = await Arweave.crypto.generateJWK(); + const userAddress = arweaveRSAModulusToAddress(jwk.n); + + await dbTestHelper.insertStubPaymentReceipt({ + top_up_quote_id: "used promo code id", + destination_address: userAddress, + payment_receipt_id: "unique id promo ineligible top up", + }); + await dbTestHelper.insertStubPaymentAdjustment({ + catalog_id: routerTestPromoCodeCatalogId, + top_up_quote_id: "used promo code id", + user_address: userAddress, + }); + + const { status, statusText, data } = await axiosPackage + .create({ + baseURL: localTestUrl, + validateStatus: () => true, + }) + // This wallet just used this code above... So we should now fail + .get( + `/v1/top-up/payment-intent/${userAddress}/usd/1000?promoCode=${routerTestPromoCode}`, + { + headers: await signedRequestHeadersFromJwk(jwk, "123"), + } + ); + + expect(data).to.equal( + `The user '${userAddress}' is ineligible for the promo code '${routerTestPromoCode}'` + ); + expect(status).to.equal(400); + expect(statusText).to.equal("Bad Request"); + }); + it("GET /top-up returns 403 for bad arweave address", async () => { const { status, statusText, data } = await axios.get( `/v1/top-up/checkout-session/BAD_ADDRESS_OF_DOOM/usd/100` @@ -393,6 +1021,15 @@ describe("Router tests", () => { expect(statusText).to.equal("Forbidden"); }); + it("GET /top-up returns 400 for bad email address", async () => { + const { status, statusText, data } = await axios.get( + `/v1/top-up/checkout-session/THISisNotEmail/usd/100?destinationAddressType=email` + ); + expect(status).to.equal(400); + expect(data).to.equal("Destination address is not a valid email!"); + expect(statusText).to.equal("Bad Request"); + }); + it("GET /top-up returns 400 for invalid payment method", async () => { const { status, data, statusText } = await axios.get( `/v1/top-up/some-method/${testAddress}/usd/101` @@ -454,9 +1091,6 @@ describe("Router tests", () => { stripe.checkout.sessions, "create" ).resolves(stripeResponseStub(checkoutSessionStub({}))); - stub(coinGeckoOracle, "getFiatPricesForOneAR").resolves( - expectedArPrices.arweave - ); // Get maximum price for each supported currency concurrently const maxPriceResponses = await Promise.all( @@ -485,10 +1119,6 @@ describe("Router tests", () => { }); it("GET /top-up returns 400 for a payment amount too large in each supported currency", async () => { - stub(coinGeckoOracle, "getFiatPricesForOneAR").resolves( - expectedArPrices.arweave - ); - for (const currencyType of supportedPaymentCurrencyTypes) { const maxAmountAllowed = currencyLimitations[currencyType].maximumPaymentAmount; @@ -510,10 +1140,6 @@ describe("Router tests", () => { }); it("GET /top-up returns 400 for a payment amount too small in each supported currency", async () => { - stub(coinGeckoOracle, "getFiatPricesForOneAR").resolves( - expectedArPrices.arweave - ); - for (const currencyType of supportedPaymentCurrencyTypes) { const minAmountAllowed = currencyLimitations[currencyType].minimumPaymentAmount; @@ -579,12 +1205,13 @@ describe("Router tests", () => { const adjustedWincTotal = new Winston("100"); stub(pricingService, "getWCForBytes").resolves({ - winc: adjustedWincTotal, + finalPrice: new FinalPrice(adjustedWincTotal), + networkPrice: new NetworkPrice(adjustedWincTotal), adjustments: [], }); const { status, statusText, data } = await axios.get( - `/v1/reserve-balance/${testAddress}?byteCount=${byteCount}&dataItemId=${stubTxId1}`, + `/v1/reserve-balance/${testAddress}?byteCount=${byteCount}&dataItemId=${stubTxId2}`, { headers: { Authorization: `Bearer ${token}`, @@ -596,7 +1223,7 @@ describe("Router tests", () => { expect(data).to.equal("100"); }); - it("GET /reserve-balance returns 200 for correct params using legacy route", async () => { + it("GET /reserve-balance returns 400 for legacy route without a data item ID", async () => { const testAddress = "a stub address 2"; await dbTestHelper.insertStubUser({ user_address: testAddress, @@ -610,7 +1237,8 @@ describe("Router tests", () => { const wincTotal = new Winston("100"); stub(pricingService, "getWCForBytes").resolves({ - winc: wincTotal, + finalPrice: new FinalPrice(wincTotal), + networkPrice: new NetworkPrice(wincTotal), adjustments: [], }); @@ -622,9 +1250,9 @@ describe("Router tests", () => { }, } ); - expect(statusText).to.equal("Balance reserved"); - expect(status).to.equal(200); - expect(data).to.equal("100"); + expect(statusText).to.equal("Bad Request"); + expect(status).to.equal(400); + expect(data).to.equal("Invalid or missing parameters"); }); it("GET /reserve-balance returns 401 for missing authorization", async () => { @@ -675,32 +1303,99 @@ describe("Router tests", () => { expect(status).to.equal(404); }); - it("GET /refund-balance returns 200 for correct params", async () => { - const winstonCredits = 1000; + it("GET /check-balance returns 200 for correct params", async () => { + const testAddress = "a unique new stub address"; + await dbTestHelper.insertStubUser({ + user_address: testAddress, + winston_credit_balance: "1000000000", + }); + + const byteCount = 1; const token = sign({}, TEST_PRIVATE_ROUTE_SECRET, { expiresIn: "1h", }); - const { status, statusText } = await axios.get( - `/v1/refund-balance/${testAddress}?winstonCredits=${winstonCredits}&dataItemId=${stubTxId1}`, + const adjustedWincTotal = new Winston("100"); + stub(pricingService, "getWCForBytes").resolves({ + finalPrice: new FinalPrice(adjustedWincTotal), + networkPrice: new NetworkPrice(adjustedWincTotal), + adjustments: [], + }); + + const { status, statusText, data } = await axios.get( + `/v1/check-balance/${testAddress}?byteCount=${byteCount}`, { headers: { Authorization: `Bearer ${token}`, }, } ); - expect(statusText).to.equal("Balance refunded"); + expect(statusText).to.equal("User has sufficient balance"); expect(status).to.equal(200); + expect(data).to.deep.equal({ + userHasSufficientBalance: true, + bytesCostInWinc: "100", + userBalanceInWinc: "1000000000", + adjustments: [], + }); + }); + + it("GET /check-balance returns 401 for missing authorization", async () => { + const byteCount = 1000; + + const { status, statusText } = await axios.get( + `/v1/check-balance/${testAddress}?byteCount=${byteCount}` + ); + expect(statusText).to.equal("Unauthorized"); + expect(status).to.equal(401); + }); + + it("GET /check-balance returns 402 for insufficient balance", async () => { + const byteCount = 10000000; + const token = sign({}, TEST_PRIVATE_ROUTE_SECRET, { + expiresIn: "1h", + }); + + const { status, statusText } = await axios.get( + `/v1/check-balance/${testAddress}?byteCount=${byteCount}`, + { + headers: { + Authorization: `Bearer ${token}`, + }, + } + ); + expect(statusText).to.equal("Insufficient balance"); + expect(status).to.equal(402); + }); + + it("GET /check-balance returns 404 if user not found", async () => { + const testAddress = "someRandomAddress"; + const byteCount = 100000; + + const token = sign({}, TEST_PRIVATE_ROUTE_SECRET, { + expiresIn: "1h", + }); + + const { status, statusText } = await axios.get( + `/v1/check-balance/${testAddress}?byteCount=${byteCount}`, + { + headers: { + Authorization: `Bearer ${token}`, + }, + } + ); + expect(statusText).to.equal("User not found"); + expect(status).to.equal(404); }); - it("GET /refund-balance returns 200 for correct params for legacy route", async () => { + it("GET /refund-balance returns 200 for correct params", async () => { const winstonCredits = 1000; const token = sign({}, TEST_PRIVATE_ROUTE_SECRET, { expiresIn: "1h", }); const { status, statusText } = await axios.get( - `/v1/refund-balance/${testAddress}/${winstonCredits}`, + `/v1/refund-balance/${testAddress}?winstonCredits=${winstonCredits}&dataItemId=${stubTxId1}`, { headers: { Authorization: `Bearer ${token}`, @@ -813,7 +1508,7 @@ describe("Router tests", () => { await new Promise((resolve) => setTimeout(resolve, 500)); const chargebackReceipt = await paymentDatabase[ - "knexReader" + "reader" ](tableNames.chargebackReceipt).where({ payment_receipt_id: disputeEventPaymentReceiptId, }); @@ -843,7 +1538,7 @@ describe("Router tests", () => { expect(winston_credit_amount).to.equal("50"); expect(chargeback_reason).to.equal("fraudulent"); - const user = await paymentDatabase["knexReader"]( + const user = await paymentDatabase["reader"]( tableNames.user ).where({ user_address: disputeEventUserAddress, @@ -869,6 +1564,7 @@ describe("Router tests", () => { top_up_quote_id: paymentSuccessTopUpQuoteId, winston_credit_amount: "500", payment_amount: "100", + quoted_payment_amount: "100", destination_address: paymentReceivedUserAddress, }); @@ -900,7 +1596,7 @@ describe("Router tests", () => { await new Promise((resolve) => setTimeout(resolve, 500)); const paymentReceipt = await paymentDatabase[ - "knexReader" + "reader" ](tableNames.paymentReceipt).where({ top_up_quote_id: paymentSuccessTopUpQuoteId, }); @@ -908,6 +1604,7 @@ describe("Router tests", () => { const { payment_amount, + quoted_payment_amount, currency_type, destination_address, destination_address_type, @@ -915,11 +1612,119 @@ describe("Router tests", () => { } = paymentReceipt[0]; expect(payment_amount).to.equal("100"); + expect(quoted_payment_amount).to.equal("100"); expect(currency_type).to.equal("usd"); expect(destination_address).to.equal(paymentReceivedUserAddress); expect(destination_address_type).to.equal("arweave"); expect(payment_provider).to.equal("stripe"); + const user = await paymentDatabase["reader"]( + tableNames.user + ).where({ + user_address: paymentReceivedUserAddress, + }); + expect(user[0].winston_credit_balance).to.equal("500"); + + webhookStub.restore(); + }); + + it("POST /stripe-webhook returns 200 for valid stripe payment success event resulting in an unredeemed gift and the database contains the correct payment receipt and unredeemed gift entities", async () => { + stub(emailProvider, "sendEmail").resolves(); + + const paymentReceivedEventId = "A Unique ID!!!"; + const testEmailAddress = "test@example.inc"; + const paymentSuccessTopUpQuoteId = "0x0987654321091"; + + await dbTestHelper.insertStubTopUpQuote({ + top_up_quote_id: paymentSuccessTopUpQuoteId, + winston_credit_amount: "500", + payment_amount: "100", + quoted_payment_amount: "100", + destination_address: testEmailAddress, + destination_address_type: "email", + gift_message: "A gift message", + }); + + const successStub = paymentIntentStub({ + id: paymentReceivedEventId, + metadata: { + topUpQuoteId: paymentSuccessTopUpQuoteId, + }, + amount: 100, + currency: "usd", + }); + + const stubEvent = stripeStubEvent({ + type: "payment_intent.succeeded", + eventObject: successStub, + }); + + const webhookStub = stub(stripe.webhooks, "constructEvent").returns( + stubEvent + ); + + const { status, statusText, data } = await axios.post(`/v1/stripe-webhook`); + + expect(status).to.equal(200); + expect(statusText).to.equal("OK"); + expect(data).to.equal("OK"); + + // wait a few seconds for the database to update since we return the response right away + await new Promise((resolve) => setTimeout(resolve, 500)); + + const paymentReceipt = await paymentDatabase[ + "writer" + ](tableNames.paymentReceipt).where({ + top_up_quote_id: paymentSuccessTopUpQuoteId, + }); + expect(paymentReceipt.length).to.equal(1); + + const { + payment_amount, + quoted_payment_amount, + currency_type, + destination_address, + destination_address_type, + payment_provider, + } = paymentReceipt[0]; + + expect(payment_amount).to.equal("100"); + expect(quoted_payment_amount).to.equal("100"); + expect(currency_type).to.equal("usd"); + expect(destination_address).to.equal(testEmailAddress); + expect(destination_address_type).to.equal("email"); + expect(payment_provider).to.equal("stripe"); + + const gift = await paymentDatabase["writer"]( + tableNames.unredeemedGift + ).where({ + payment_receipt_id: paymentReceipt[0].payment_receipt_id, + }); + expect(gift.length).to.equal(1); + + const { + creation_date, + expiration_date, + gifted_winc_amount, + payment_receipt_id, + recipient_email, + gift_message, + } = gift[0]; + + expect(creation_date).to.exist; + // expect expiration to be gift creation plus 1 year + expect(new Date(expiration_date).toISOString()).to.equal( + new Date( + new Date(creation_date).setFullYear( + new Date(creation_date).getFullYear() + 1 + ) + ).toISOString() + ); + expect(gifted_winc_amount).to.equal("500"); + expect(payment_receipt_id).to.equal(paymentReceipt[0].payment_receipt_id); + expect(recipient_email).to.equal(testEmailAddress); + expect(gift_message).to.equal("A gift message"); + webhookStub.restore(); }); @@ -932,6 +1737,210 @@ describe("Router tests", () => { expect(statusText).to.equal("Bad Request"); expect(data).to.equal("Webhook Error!"); }); + + it("GET /rates returns 502 if unable to fetch prices", async () => { + stub(pricingService, "getWCForBytes").throws(Error("Serious failure")); + + const { status, statusText } = await axios.get(`/v1/rates`); + + expect(status).to.equal(502); + expect(statusText).to.equal("Bad Gateway"); + }); + + it("GET /redeem returns 200 for valid params", async () => { + const destinationAddress = "validArweaveAddressNeedsFortyThreeCharacter"; + const paymentReceiptId = "unique paymentReceiptId"; + const emailAddress = "test@example.inc"; + const giftMessage = "hello the world!"; + + const paymentReceiptDBInsert: PaymentReceiptDBInsert = { + top_up_quote_id: "required top up id", + payment_receipt_id: paymentReceiptId, + payment_amount: "100", + quoted_payment_amount: "100", + currency_type: "email", + destination_address: emailAddress, + destination_address_type: "arweave", + payment_provider: "stripe", + quote_creation_date: oneHourAgo, + quote_expiration_date: oneHourFromNow, + winston_credit_amount: "100", + gift_message: giftMessage, + }; + await paymentDatabase["writer"]( + tableNames.paymentReceipt + ).insert(paymentReceiptDBInsert); + const unredeemedGiftDbInsert: UnredeemedGiftDBInsert = { + gifted_winc_amount: "100", + payment_receipt_id: paymentReceiptId, + recipient_email: emailAddress, + gift_message: giftMessage, + }; + await paymentDatabase["writer"]( + tableNames.unredeemedGift + ).insert(unredeemedGiftDbInsert); + + const { status, statusText, data } = await axios.get( + `/v1/redeem?destinationAddress=${destinationAddress}&id=${paymentReceiptId}&email=${emailAddress}` + ); + + expect(status).to.equal(200); + expect(statusText).to.equal("OK"); + + const { message, userBalance, userAddress, userCreationDate } = data; + expect(message).to.equal("Payment receipt redeemed for 100 winc!"); + expect(userBalance).to.equal("100"); + expect(userAddress).to.equal(destinationAddress); + expect(userCreationDate).to.exist; + + const userDbResult = await paymentDatabase["reader"]( + tableNames.user + ).where({ + user_address: destinationAddress, + }); + expect(userDbResult.length).to.equal(1); + expect(userDbResult[0].winston_credit_balance).to.equal("100"); + + const unredeemedGiftDbResult = await paymentDatabase[ + "reader" + ](tableNames.unredeemedGift).where({ + payment_receipt_id: paymentReceiptId, + }); + expect(unredeemedGiftDbResult.length).to.equal(0); + + const redeemedGiftDbResult = await paymentDatabase[ + "reader" + ](tableNames.redeemedGift).where({ + payment_receipt_id: paymentReceiptId, + }); + expect(redeemedGiftDbResult.length).to.equal(1); + + const { + payment_receipt_id, + recipient_email, + gift_message, + creation_date, + destination_address, + expiration_date, + gifted_winc_amount, + redemption_date, + } = redeemedGiftDbResult[0]; + + expect(payment_receipt_id).to.equal(paymentReceiptId); + expect(recipient_email).to.equal(emailAddress); + expect(gift_message).to.equal(giftMessage); + expect(creation_date).to.exist; + expect(destination_address).to.equal(destinationAddress); + expect(expiration_date).to.exist; + expect(gifted_winc_amount).to.equal("100"); + expect(redemption_date).to.exist; + }); + + it("GET /redeem returns 400 for invalid email", async () => { + const destinationAddress = "validArweaveAddressNeedsFortyThreeCharacter"; + const paymentReceiptId = "unique paymentReceiptId 21e12"; + const emailAddress = "invalid email"; + + const { status, statusText, data } = await axios.get( + `/v1/redeem?destinationAddress=${destinationAddress}&id=${paymentReceiptId}&email=${emailAddress}` + ); + + expect(status).to.equal(400); + expect(statusText).to.equal("Bad Request"); + expect(data).to.equal( + "Provided recipient email address is not a valid email!" + ); + }); + + it("GET /redeem returns 400 for invalid destination address", async () => { + const destinationAddress = "invalidArweaveAddressNeedsFortyThreeCharacter"; + const paymentReceiptId = "unique das paymentReceiptId 21e12"; + const emailAddress = "fake@example.inc"; + + const { status, statusText, data } = await axios.get( + `/v1/redeem?destinationAddress=${destinationAddress}&id=${paymentReceiptId}&email=${emailAddress}` + ); + + expect(status).to.equal(400); + expect(statusText).to.equal("Bad Request"); + expect(data).to.equal( + "Provided destination address is not a valid Arweave native address!" + ); + }); + + it("GET /redeem returns 400 for non matching recipient email", async () => { + const destinationAddress = "validArweaveAddressNeedsFortyThreeCharacter"; + const paymentReceiptId = "unique paymentReceiptId 231"; + const emailAddress = "fake@inc.com"; + + const paymentReceiptDBInsert: PaymentReceiptDBInsert = { + top_up_quote_id: "required top up id!", + payment_receipt_id: paymentReceiptId, + payment_amount: "100", + quoted_payment_amount: "100", + currency_type: "email", + destination_address: emailAddress, + destination_address_type: "arweave", + payment_provider: "stripe", + quote_creation_date: oneHourAgo, + quote_expiration_date: oneHourFromNow, + winston_credit_amount: "100", + gift_message: "A gift message", + }; + await paymentDatabase["writer"]( + tableNames.paymentReceipt + ).insert(paymentReceiptDBInsert); + + const unredeemedGiftDbInsert: UnredeemedGiftDBInsert = { + gifted_winc_amount: "100", + payment_receipt_id: paymentReceiptId, + recipient_email: emailAddress, + gift_message: "A gift message", + }; + await paymentDatabase["writer"]( + tableNames.unredeemedGift + ).insert(unredeemedGiftDbInsert); + + const { status, statusText, data } = await axios.get( + `/v1/redeem?destinationAddress=${destinationAddress}&id=${paymentReceiptId}&email=wrong@email.test` + ); + + expect(status).to.equal(400); + expect(statusText).to.equal("Bad Request"); + expect(data).to.equal("Failure to redeem payment receipt!"); + }); + + it("GET /redeem returns 400 for not found payment receipt id", async () => { + const destinationAddress = "validArweaveAddressNeedsFortyThreeCharacter"; + const paymentReceiptId = "unique paymentReceiptId 221"; + const emailAddress = "fake@unique.inc"; + + const { status, statusText, data } = await axios.get( + `/v1/redeem?destinationAddress=${destinationAddress}&id=${paymentReceiptId}&email=${emailAddress}` + ); + + expect(status).to.equal(400); + expect(statusText).to.equal("Bad Request"); + expect(data).to.equal("Failure to redeem payment receipt!"); + }); + + it("GET /redeem returns 503 for unexpected database error", async () => { + const destinationAddress = "validArweaveAddressNeedsFortyThreeCharacter"; + const paymentReceiptId = "unique paymentReceiptId 141"; + const emailAddress = "fake@unique.inc"; + + stub(paymentDatabase, "redeemGift").throws(); + + const { status, statusText, data } = await axios.get( + `/v1/redeem?destinationAddress=${destinationAddress}&id=${paymentReceiptId}&email=${emailAddress}` + ); + + expect(status).to.equal(503); + expect(statusText).to.equal("Service Unavailable"); + expect(data).to.equal( + "Error while redeeming payment receipt. Unable to reach Database!" + ); + }); }); describe("Caching behavior tests", () => { diff --git a/tests/schema.int.test.ts b/tests/schema.int.test.ts deleted file mode 100644 index 14b33ef..0000000 --- a/tests/schema.int.test.ts +++ /dev/null @@ -1,103 +0,0 @@ -/** - * Copyright (C) 2022-2023 Permanent Data Solutions, Inc. All Rights Reserved. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ -import { expect } from "chai"; -import Knex from "knex"; - -import * as knexConfig from "../src/database/knexfile"; -import { Schema } from "../src/database/schema"; -import { expectedColumnInfo } from "./helpers/testExpectations"; -import { listTables } from "./helpers/testHelpers"; - -/** Knex instance connected to a PostgreSQL database */ -const knex = Knex(knexConfig); - -describe("Schema class", () => { - after(async function () { - // Adjust timeout, this block sometimes takes longer than 3000ms during GitHub CI - this.timeout(6_000); - - // Run a new schema create after the rollback test so database will be as expected for integration tests - await Schema.create(knex); - await Schema.migrateToAuditLog(knex); - - // Run these tests that depend on db after schema tests - require("./postgres.int.spec"); - }); - - it("after running latest knex migrations with knex CLI from docker-test.sh, all expected tables exists", async () => { - const allTables = await listTables(knex); - - expect(allTables.rows.map((t) => t.table_name)).to.deep.equal([ - // Tables are returned alphabetized - "audit_log", - "chargeback_receipt", - "failed_top_up_quote", - "knex_migrations", - "knex_migrations_lock", - "payment_receipt", - "top_up_quote", - "user", - ]); - }); - - it("creates a `user` table that has the expected column structure", async () => { - const columnInfo = await knex("user").columnInfo(); - expect(columnInfo).to.deep.equal(expectedColumnInfo.user); - }); - - it("creates a `top_up_quote` table that has the expected column structure", async () => { - const columnInfo = await knex("top_up_quote").columnInfo(); - expect(columnInfo).to.deep.equal(expectedColumnInfo.top_up_quote); - }); - - it("creates a `failed_top_up_quote` table that has the expected column structure", async () => { - const columnInfo = await knex("failed_top_up_quote").columnInfo(); - expect(columnInfo).to.deep.equal({ - ...expectedColumnInfo.top_up_quote, - ...expectedColumnInfo.failed_top_up_quote, - }); - }); - - it("creates a `payment_receipt` table that has the expected column structure", async () => { - const columnInfo = await knex("payment_receipt").columnInfo(); - expect(columnInfo).to.deep.equal({ - ...expectedColumnInfo.top_up_quote, - ...expectedColumnInfo.payment_receipt, - }); - }); - - it("creates a `chargeback_receipt` table that has the expected column structure", async () => { - const columnInfo = await knex("chargeback_receipt").columnInfo(); - expect(columnInfo).to.deep.equal({ - ...expectedColumnInfo.top_up_quote, - ...expectedColumnInfo.payment_receipt, - ...expectedColumnInfo.chargeback_receipt, - }); - }); - - it("rollback schema public static methods remove all expected tables", async () => { - await Schema.rollbackFromAuditLog(knex); - await Schema.rollback(knex); - - const allTables = await listTables(knex); - - expect(allTables.rows.map((t) => t.table_name)).to.deep.equal([ - "knex_migrations", - "knex_migrations_lock", - ]); - }); -}); diff --git a/tests/testSetup.ts b/tests/testSetup.ts index 7dd8fe8..34e8a75 100644 --- a/tests/testSetup.ts +++ b/tests/testSetup.ts @@ -21,6 +21,8 @@ process.env.PORT ??= "1234"; process.env.DISABLE_LOGS ??= "true"; process.env.STRIPE_SECRET_KEY ??= "test"; process.env.STRIPE_WEBHOOK_SECRET ??= "test"; +process.env.MANDRILL_API_KEY ??= "test"; +process.env.GIFTING_ENABLED ??= "true"; // Restores the default sandbox after every test exports.mochaHooks = { diff --git a/yarn.lock b/yarn.lock index 8f18719..babf72b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5,6 +5,13 @@ __metadata: version: 6 cacheKey: 8 +"@aashutoshrathi/word-wrap@npm:^1.2.3": + version: 1.2.6 + resolution: "@aashutoshrathi/word-wrap@npm:1.2.6" + checksum: ada901b9e7c680d190f1d012c84217ce0063d8f5c5a7725bb91ec3c5ed99bb7572680eb2d2938a531ccbaec39a95422fcd8a6b4a13110c7d98dd75402f66a0cd + languageName: node + linkType: hard + "@alexsasharegan/simple-cache@npm:^3.3.3": version: 3.3.3 resolution: "@alexsasharegan/simple-cache@npm:3.3.3" @@ -15,12 +22,21 @@ __metadata: linkType: hard "@ampproject/remapping@npm:^2.1.0, @ampproject/remapping@npm:^2.2.0": - version: 2.2.0 - resolution: "@ampproject/remapping@npm:2.2.0" + version: 2.2.1 + resolution: "@ampproject/remapping@npm:2.2.1" dependencies: - "@jridgewell/gen-mapping": ^0.1.0 + "@jridgewell/gen-mapping": ^0.3.0 "@jridgewell/trace-mapping": ^0.3.9 - checksum: d74d170d06468913921d72430259424b7e4c826b5a7d39ff839a29d547efb97dc577caa8ba3fb5cf023624e9af9d09651afc3d4112a45e2050328abc9b3a2292 + checksum: 03c04fd526acc64a1f4df22651186f3e5ef0a9d6d6530ce4482ec9841269cf7a11dbb8af79237c282d721c5312024ff17529cd72cc4768c11e999b58e2302079 + languageName: node + linkType: hard + +"@ardrive/ardrive-promise-cache@npm:^1.1.4": + version: 1.1.4 + resolution: "@ardrive/ardrive-promise-cache@npm:1.1.4" + dependencies: + "@alexsasharegan/simple-cache": ^3.3.3 + checksum: c92f3f745c2138b2d62c53d6a394900f1691dfe696106cf99d1ca3e71fc284c34cf2c84ceb5ad224ad19964397b2097a41bd9f54bc93464182c095e96b400cba languageName: node linkType: hard @@ -91,1158 +107,457 @@ __metadata: languageName: node linkType: hard -"@aws-sdk/abort-controller@npm:3.303.0": - version: 3.303.0 - resolution: "@aws-sdk/abort-controller@npm:3.303.0" - dependencies: - "@aws-sdk/types": 3.303.0 - tslib: ^2.5.0 - checksum: 1c603cb7aff44c5beb061214445176a8c31f3ee4079544c184bdddb5b0a2d2c4ec542873d166889296c47f9b69a08c0cb3ef6688e47c1fd252d548cd906d31ea - languageName: node - linkType: hard - "@aws-sdk/client-secrets-manager@npm:^3.290.0": - version: 3.303.0 - resolution: "@aws-sdk/client-secrets-manager@npm:3.303.0" + version: 3.382.0 + resolution: "@aws-sdk/client-secrets-manager@npm:3.382.0" dependencies: "@aws-crypto/sha256-browser": 3.0.0 "@aws-crypto/sha256-js": 3.0.0 - "@aws-sdk/client-sts": 3.303.0 - "@aws-sdk/config-resolver": 3.303.0 - "@aws-sdk/credential-provider-node": 3.303.0 - "@aws-sdk/fetch-http-handler": 3.303.0 - "@aws-sdk/hash-node": 3.303.0 - "@aws-sdk/invalid-dependency": 3.303.0 - "@aws-sdk/middleware-content-length": 3.303.0 - "@aws-sdk/middleware-endpoint": 3.303.0 - "@aws-sdk/middleware-host-header": 3.303.0 - "@aws-sdk/middleware-logger": 3.303.0 - "@aws-sdk/middleware-recursion-detection": 3.303.0 - "@aws-sdk/middleware-retry": 3.303.0 - "@aws-sdk/middleware-serde": 3.303.0 - "@aws-sdk/middleware-signing": 3.303.0 - "@aws-sdk/middleware-stack": 3.303.0 - "@aws-sdk/middleware-user-agent": 3.303.0 - "@aws-sdk/node-config-provider": 3.303.0 - "@aws-sdk/node-http-handler": 3.303.0 - "@aws-sdk/protocol-http": 3.303.0 - "@aws-sdk/smithy-client": 3.303.0 - "@aws-sdk/types": 3.303.0 - "@aws-sdk/url-parser": 3.303.0 - "@aws-sdk/util-base64": 3.303.0 - "@aws-sdk/util-body-length-browser": 3.303.0 - "@aws-sdk/util-body-length-node": 3.303.0 - "@aws-sdk/util-defaults-mode-browser": 3.303.0 - "@aws-sdk/util-defaults-mode-node": 3.303.0 - "@aws-sdk/util-endpoints": 3.303.0 - "@aws-sdk/util-retry": 3.303.0 - "@aws-sdk/util-user-agent-browser": 3.303.0 - "@aws-sdk/util-user-agent-node": 3.303.0 - "@aws-sdk/util-utf8": 3.303.0 + "@aws-sdk/client-sts": 3.382.0 + "@aws-sdk/credential-provider-node": 3.382.0 + "@aws-sdk/middleware-host-header": 3.379.1 + "@aws-sdk/middleware-logger": 3.378.0 + "@aws-sdk/middleware-recursion-detection": 3.378.0 + "@aws-sdk/middleware-signing": 3.379.1 + "@aws-sdk/middleware-user-agent": 3.382.0 + "@aws-sdk/types": 3.378.0 + "@aws-sdk/util-endpoints": 3.382.0 + "@aws-sdk/util-user-agent-browser": 3.378.0 + "@aws-sdk/util-user-agent-node": 3.378.0 + "@smithy/config-resolver": ^2.0.1 + "@smithy/fetch-http-handler": ^2.0.1 + "@smithy/hash-node": ^2.0.1 + "@smithy/invalid-dependency": ^2.0.1 + "@smithy/middleware-content-length": ^2.0.1 + "@smithy/middleware-endpoint": ^2.0.1 + "@smithy/middleware-retry": ^2.0.1 + "@smithy/middleware-serde": ^2.0.1 + "@smithy/middleware-stack": ^2.0.0 + "@smithy/node-config-provider": ^2.0.1 + "@smithy/node-http-handler": ^2.0.1 + "@smithy/protocol-http": ^2.0.1 + "@smithy/smithy-client": ^2.0.1 + "@smithy/types": ^2.0.2 + "@smithy/url-parser": ^2.0.1 + "@smithy/util-base64": ^2.0.0 + "@smithy/util-body-length-browser": ^2.0.0 + "@smithy/util-body-length-node": ^2.0.0 + "@smithy/util-defaults-mode-browser": ^2.0.1 + "@smithy/util-defaults-mode-node": ^2.0.1 + "@smithy/util-retry": ^2.0.0 + "@smithy/util-utf8": ^2.0.0 tslib: ^2.5.0 uuid: ^8.3.2 - checksum: fb256258007035410233d7d0ff988e66e337181402b1c997e5d594f6c9c4087fc0b60fcb94d4308a9223aeb4d9528915ab37efffc606f7b61df81faa73dc6131 + checksum: fb05350f5c2010fe7a6d8c3c65b0a6e1318250c612840fc3b22e7e44acd2cbd959ef1f1e50af74a4ecdb18a2dcbedcc5fc0ec3e51a772a2474eda2a853f8815b languageName: node linkType: hard "@aws-sdk/client-ssm@npm:^3.369.0": - version: 3.369.0 - resolution: "@aws-sdk/client-ssm@npm:3.369.0" + version: 3.382.0 + resolution: "@aws-sdk/client-ssm@npm:3.382.0" dependencies: "@aws-crypto/sha256-browser": 3.0.0 "@aws-crypto/sha256-js": 3.0.0 - "@aws-sdk/client-sts": 3.369.0 - "@aws-sdk/credential-provider-node": 3.369.0 - "@aws-sdk/middleware-host-header": 3.369.0 - "@aws-sdk/middleware-logger": 3.369.0 - "@aws-sdk/middleware-recursion-detection": 3.369.0 - "@aws-sdk/middleware-signing": 3.369.0 - "@aws-sdk/middleware-user-agent": 3.369.0 - "@aws-sdk/types": 3.369.0 - "@aws-sdk/util-endpoints": 3.369.0 - "@aws-sdk/util-user-agent-browser": 3.369.0 - "@aws-sdk/util-user-agent-node": 3.369.0 - "@smithy/config-resolver": ^1.0.1 - "@smithy/fetch-http-handler": ^1.0.1 - "@smithy/hash-node": ^1.0.1 - "@smithy/invalid-dependency": ^1.0.1 - "@smithy/middleware-content-length": ^1.0.1 - "@smithy/middleware-endpoint": ^1.0.1 - "@smithy/middleware-retry": ^1.0.2 - "@smithy/middleware-serde": ^1.0.1 - "@smithy/middleware-stack": ^1.0.1 - "@smithy/node-config-provider": ^1.0.1 - "@smithy/node-http-handler": ^1.0.2 - "@smithy/protocol-http": ^1.0.1 - "@smithy/smithy-client": ^1.0.3 - "@smithy/types": ^1.1.0 - "@smithy/url-parser": ^1.0.1 - "@smithy/util-base64": ^1.0.1 - "@smithy/util-body-length-browser": ^1.0.1 - "@smithy/util-body-length-node": ^1.0.1 - "@smithy/util-defaults-mode-browser": ^1.0.1 - "@smithy/util-defaults-mode-node": ^1.0.1 - "@smithy/util-retry": ^1.0.2 - "@smithy/util-utf8": ^1.0.1 - "@smithy/util-waiter": ^1.0.1 + "@aws-sdk/client-sts": 3.382.0 + "@aws-sdk/credential-provider-node": 3.382.0 + "@aws-sdk/middleware-host-header": 3.379.1 + "@aws-sdk/middleware-logger": 3.378.0 + "@aws-sdk/middleware-recursion-detection": 3.378.0 + "@aws-sdk/middleware-signing": 3.379.1 + "@aws-sdk/middleware-user-agent": 3.382.0 + "@aws-sdk/types": 3.378.0 + "@aws-sdk/util-endpoints": 3.382.0 + "@aws-sdk/util-user-agent-browser": 3.378.0 + "@aws-sdk/util-user-agent-node": 3.378.0 + "@smithy/config-resolver": ^2.0.1 + "@smithy/fetch-http-handler": ^2.0.1 + "@smithy/hash-node": ^2.0.1 + "@smithy/invalid-dependency": ^2.0.1 + "@smithy/middleware-content-length": ^2.0.1 + "@smithy/middleware-endpoint": ^2.0.1 + "@smithy/middleware-retry": ^2.0.1 + "@smithy/middleware-serde": ^2.0.1 + "@smithy/middleware-stack": ^2.0.0 + "@smithy/node-config-provider": ^2.0.1 + "@smithy/node-http-handler": ^2.0.1 + "@smithy/protocol-http": ^2.0.1 + "@smithy/smithy-client": ^2.0.1 + "@smithy/types": ^2.0.2 + "@smithy/url-parser": ^2.0.1 + "@smithy/util-base64": ^2.0.0 + "@smithy/util-body-length-browser": ^2.0.0 + "@smithy/util-body-length-node": ^2.0.0 + "@smithy/util-defaults-mode-browser": ^2.0.1 + "@smithy/util-defaults-mode-node": ^2.0.1 + "@smithy/util-retry": ^2.0.0 + "@smithy/util-utf8": ^2.0.0 + "@smithy/util-waiter": ^2.0.1 tslib: ^2.5.0 uuid: ^8.3.2 - checksum: d47a05d349586be8eb7e6800a691f8902ee89d87905fc5eb27ed011c3b9a42f9a13f09d2f43a965af6172325320511db5bfc3bf446eb22a1c66b230c802f4f70 - languageName: node - linkType: hard - -"@aws-sdk/client-sso-oidc@npm:3.303.0": - version: 3.303.0 - resolution: "@aws-sdk/client-sso-oidc@npm:3.303.0" - dependencies: - "@aws-crypto/sha256-browser": 3.0.0 - "@aws-crypto/sha256-js": 3.0.0 - "@aws-sdk/config-resolver": 3.303.0 - "@aws-sdk/fetch-http-handler": 3.303.0 - "@aws-sdk/hash-node": 3.303.0 - "@aws-sdk/invalid-dependency": 3.303.0 - "@aws-sdk/middleware-content-length": 3.303.0 - "@aws-sdk/middleware-endpoint": 3.303.0 - "@aws-sdk/middleware-host-header": 3.303.0 - "@aws-sdk/middleware-logger": 3.303.0 - "@aws-sdk/middleware-recursion-detection": 3.303.0 - "@aws-sdk/middleware-retry": 3.303.0 - "@aws-sdk/middleware-serde": 3.303.0 - "@aws-sdk/middleware-stack": 3.303.0 - "@aws-sdk/middleware-user-agent": 3.303.0 - "@aws-sdk/node-config-provider": 3.303.0 - "@aws-sdk/node-http-handler": 3.303.0 - "@aws-sdk/protocol-http": 3.303.0 - "@aws-sdk/smithy-client": 3.303.0 - "@aws-sdk/types": 3.303.0 - "@aws-sdk/url-parser": 3.303.0 - "@aws-sdk/util-base64": 3.303.0 - "@aws-sdk/util-body-length-browser": 3.303.0 - "@aws-sdk/util-body-length-node": 3.303.0 - "@aws-sdk/util-defaults-mode-browser": 3.303.0 - "@aws-sdk/util-defaults-mode-node": 3.303.0 - "@aws-sdk/util-endpoints": 3.303.0 - "@aws-sdk/util-retry": 3.303.0 - "@aws-sdk/util-user-agent-browser": 3.303.0 - "@aws-sdk/util-user-agent-node": 3.303.0 - "@aws-sdk/util-utf8": 3.303.0 - tslib: ^2.5.0 - checksum: 9ae064a880f6015b62384b490fd3f2b0ea893b8959c4215537beb8b815d81e5995f76db674fde9af69da4a794f7a1daf6a4a4f19e5e7d01ce2e4f0c066ff45af - languageName: node - linkType: hard - -"@aws-sdk/client-sso-oidc@npm:3.369.0": - version: 3.369.0 - resolution: "@aws-sdk/client-sso-oidc@npm:3.369.0" - dependencies: - "@aws-crypto/sha256-browser": 3.0.0 - "@aws-crypto/sha256-js": 3.0.0 - "@aws-sdk/middleware-host-header": 3.369.0 - "@aws-sdk/middleware-logger": 3.369.0 - "@aws-sdk/middleware-recursion-detection": 3.369.0 - "@aws-sdk/middleware-user-agent": 3.369.0 - "@aws-sdk/types": 3.369.0 - "@aws-sdk/util-endpoints": 3.369.0 - "@aws-sdk/util-user-agent-browser": 3.369.0 - "@aws-sdk/util-user-agent-node": 3.369.0 - "@smithy/config-resolver": ^1.0.1 - "@smithy/fetch-http-handler": ^1.0.1 - "@smithy/hash-node": ^1.0.1 - "@smithy/invalid-dependency": ^1.0.1 - "@smithy/middleware-content-length": ^1.0.1 - "@smithy/middleware-endpoint": ^1.0.1 - "@smithy/middleware-retry": ^1.0.2 - "@smithy/middleware-serde": ^1.0.1 - "@smithy/middleware-stack": ^1.0.1 - "@smithy/node-config-provider": ^1.0.1 - "@smithy/node-http-handler": ^1.0.2 - "@smithy/protocol-http": ^1.0.1 - "@smithy/smithy-client": ^1.0.3 - "@smithy/types": ^1.1.0 - "@smithy/url-parser": ^1.0.1 - "@smithy/util-base64": ^1.0.1 - "@smithy/util-body-length-browser": ^1.0.1 - "@smithy/util-body-length-node": ^1.0.1 - "@smithy/util-defaults-mode-browser": ^1.0.1 - "@smithy/util-defaults-mode-node": ^1.0.1 - "@smithy/util-retry": ^1.0.2 - "@smithy/util-utf8": ^1.0.1 - tslib: ^2.5.0 - checksum: b45e904bf631c5649786eb4107fbe83021fa8952e6728c7e38788d05efe7cbe9c7ac607fe0b529a3cfbd5feec4503fce4752a6afa7486c95bb17f9cc35dd7ecb - languageName: node - linkType: hard - -"@aws-sdk/client-sso@npm:3.303.0": - version: 3.303.0 - resolution: "@aws-sdk/client-sso@npm:3.303.0" - dependencies: - "@aws-crypto/sha256-browser": 3.0.0 - "@aws-crypto/sha256-js": 3.0.0 - "@aws-sdk/config-resolver": 3.303.0 - "@aws-sdk/fetch-http-handler": 3.303.0 - "@aws-sdk/hash-node": 3.303.0 - "@aws-sdk/invalid-dependency": 3.303.0 - "@aws-sdk/middleware-content-length": 3.303.0 - "@aws-sdk/middleware-endpoint": 3.303.0 - "@aws-sdk/middleware-host-header": 3.303.0 - "@aws-sdk/middleware-logger": 3.303.0 - "@aws-sdk/middleware-recursion-detection": 3.303.0 - "@aws-sdk/middleware-retry": 3.303.0 - "@aws-sdk/middleware-serde": 3.303.0 - "@aws-sdk/middleware-stack": 3.303.0 - "@aws-sdk/middleware-user-agent": 3.303.0 - "@aws-sdk/node-config-provider": 3.303.0 - "@aws-sdk/node-http-handler": 3.303.0 - "@aws-sdk/protocol-http": 3.303.0 - "@aws-sdk/smithy-client": 3.303.0 - "@aws-sdk/types": 3.303.0 - "@aws-sdk/url-parser": 3.303.0 - "@aws-sdk/util-base64": 3.303.0 - "@aws-sdk/util-body-length-browser": 3.303.0 - "@aws-sdk/util-body-length-node": 3.303.0 - "@aws-sdk/util-defaults-mode-browser": 3.303.0 - "@aws-sdk/util-defaults-mode-node": 3.303.0 - "@aws-sdk/util-endpoints": 3.303.0 - "@aws-sdk/util-retry": 3.303.0 - "@aws-sdk/util-user-agent-browser": 3.303.0 - "@aws-sdk/util-user-agent-node": 3.303.0 - "@aws-sdk/util-utf8": 3.303.0 - tslib: ^2.5.0 - checksum: f0ff09fa642f0d2005b8001fad001b4cb00dbfa14b513635c878c25893ddb70dd56bbe6c19d23fd0c5f5c98c6641fa67e428eee164b0b96493f570005823402a + checksum: 350e2f3bf489d5faa08fc5b92802133c6557980e764296f4e598de1d069065e136aa5254322d11e841531cc43c96439be9a738017cb7e7205b576e612d1bd814 languageName: node linkType: hard -"@aws-sdk/client-sso@npm:3.369.0": - version: 3.369.0 - resolution: "@aws-sdk/client-sso@npm:3.369.0" +"@aws-sdk/client-sso-oidc@npm:3.382.0": + version: 3.382.0 + resolution: "@aws-sdk/client-sso-oidc@npm:3.382.0" dependencies: "@aws-crypto/sha256-browser": 3.0.0 "@aws-crypto/sha256-js": 3.0.0 - "@aws-sdk/middleware-host-header": 3.369.0 - "@aws-sdk/middleware-logger": 3.369.0 - "@aws-sdk/middleware-recursion-detection": 3.369.0 - "@aws-sdk/middleware-user-agent": 3.369.0 - "@aws-sdk/types": 3.369.0 - "@aws-sdk/util-endpoints": 3.369.0 - "@aws-sdk/util-user-agent-browser": 3.369.0 - "@aws-sdk/util-user-agent-node": 3.369.0 - "@smithy/config-resolver": ^1.0.1 - "@smithy/fetch-http-handler": ^1.0.1 - "@smithy/hash-node": ^1.0.1 - "@smithy/invalid-dependency": ^1.0.1 - "@smithy/middleware-content-length": ^1.0.1 - "@smithy/middleware-endpoint": ^1.0.1 - "@smithy/middleware-retry": ^1.0.2 - "@smithy/middleware-serde": ^1.0.1 - "@smithy/middleware-stack": ^1.0.1 - "@smithy/node-config-provider": ^1.0.1 - "@smithy/node-http-handler": ^1.0.2 - "@smithy/protocol-http": ^1.0.1 - "@smithy/smithy-client": ^1.0.3 - "@smithy/types": ^1.1.0 - "@smithy/url-parser": ^1.0.1 - "@smithy/util-base64": ^1.0.1 - "@smithy/util-body-length-browser": ^1.0.1 - "@smithy/util-body-length-node": ^1.0.1 - "@smithy/util-defaults-mode-browser": ^1.0.1 - "@smithy/util-defaults-mode-node": ^1.0.1 - "@smithy/util-retry": ^1.0.2 - "@smithy/util-utf8": ^1.0.1 + "@aws-sdk/middleware-host-header": 3.379.1 + "@aws-sdk/middleware-logger": 3.378.0 + "@aws-sdk/middleware-recursion-detection": 3.378.0 + "@aws-sdk/middleware-user-agent": 3.382.0 + "@aws-sdk/types": 3.378.0 + "@aws-sdk/util-endpoints": 3.382.0 + "@aws-sdk/util-user-agent-browser": 3.378.0 + "@aws-sdk/util-user-agent-node": 3.378.0 + "@smithy/config-resolver": ^2.0.1 + "@smithy/fetch-http-handler": ^2.0.1 + "@smithy/hash-node": ^2.0.1 + "@smithy/invalid-dependency": ^2.0.1 + "@smithy/middleware-content-length": ^2.0.1 + "@smithy/middleware-endpoint": ^2.0.1 + "@smithy/middleware-retry": ^2.0.1 + "@smithy/middleware-serde": ^2.0.1 + "@smithy/middleware-stack": ^2.0.0 + "@smithy/node-config-provider": ^2.0.1 + "@smithy/node-http-handler": ^2.0.1 + "@smithy/protocol-http": ^2.0.1 + "@smithy/smithy-client": ^2.0.1 + "@smithy/types": ^2.0.2 + "@smithy/url-parser": ^2.0.1 + "@smithy/util-base64": ^2.0.0 + "@smithy/util-body-length-browser": ^2.0.0 + "@smithy/util-body-length-node": ^2.0.0 + "@smithy/util-defaults-mode-browser": ^2.0.1 + "@smithy/util-defaults-mode-node": ^2.0.1 + "@smithy/util-retry": ^2.0.0 + "@smithy/util-utf8": ^2.0.0 tslib: ^2.5.0 - checksum: f5842bf628b48d0e44a4a47b4771e56641da0c1bf37c244dd6ba779d7339c8ff5025b46003e55a83ab7cfb19b64b6fe025e7d803cbabae1ccda1f84ae4465564 + checksum: 8e63db2ed9d27326d4afe897f7debbe4d7f6c2b27015d5a5e2ce0656adc3757860c313403c1f0e5bfb6790e610d63c8ef3d6ce103abaa07155b4578e4fae768d languageName: node linkType: hard -"@aws-sdk/client-sts@npm:3.303.0": - version: 3.303.0 - resolution: "@aws-sdk/client-sts@npm:3.303.0" +"@aws-sdk/client-sso@npm:3.382.0": + version: 3.382.0 + resolution: "@aws-sdk/client-sso@npm:3.382.0" dependencies: "@aws-crypto/sha256-browser": 3.0.0 "@aws-crypto/sha256-js": 3.0.0 - "@aws-sdk/config-resolver": 3.303.0 - "@aws-sdk/credential-provider-node": 3.303.0 - "@aws-sdk/fetch-http-handler": 3.303.0 - "@aws-sdk/hash-node": 3.303.0 - "@aws-sdk/invalid-dependency": 3.303.0 - "@aws-sdk/middleware-content-length": 3.303.0 - "@aws-sdk/middleware-endpoint": 3.303.0 - "@aws-sdk/middleware-host-header": 3.303.0 - "@aws-sdk/middleware-logger": 3.303.0 - "@aws-sdk/middleware-recursion-detection": 3.303.0 - "@aws-sdk/middleware-retry": 3.303.0 - "@aws-sdk/middleware-sdk-sts": 3.303.0 - "@aws-sdk/middleware-serde": 3.303.0 - "@aws-sdk/middleware-signing": 3.303.0 - "@aws-sdk/middleware-stack": 3.303.0 - "@aws-sdk/middleware-user-agent": 3.303.0 - "@aws-sdk/node-config-provider": 3.303.0 - "@aws-sdk/node-http-handler": 3.303.0 - "@aws-sdk/protocol-http": 3.303.0 - "@aws-sdk/smithy-client": 3.303.0 - "@aws-sdk/types": 3.303.0 - "@aws-sdk/url-parser": 3.303.0 - "@aws-sdk/util-base64": 3.303.0 - "@aws-sdk/util-body-length-browser": 3.303.0 - "@aws-sdk/util-body-length-node": 3.303.0 - "@aws-sdk/util-defaults-mode-browser": 3.303.0 - "@aws-sdk/util-defaults-mode-node": 3.303.0 - "@aws-sdk/util-endpoints": 3.303.0 - "@aws-sdk/util-retry": 3.303.0 - "@aws-sdk/util-user-agent-browser": 3.303.0 - "@aws-sdk/util-user-agent-node": 3.303.0 - "@aws-sdk/util-utf8": 3.303.0 - fast-xml-parser: 4.1.2 + "@aws-sdk/middleware-host-header": 3.379.1 + "@aws-sdk/middleware-logger": 3.378.0 + "@aws-sdk/middleware-recursion-detection": 3.378.0 + "@aws-sdk/middleware-user-agent": 3.382.0 + "@aws-sdk/types": 3.378.0 + "@aws-sdk/util-endpoints": 3.382.0 + "@aws-sdk/util-user-agent-browser": 3.378.0 + "@aws-sdk/util-user-agent-node": 3.378.0 + "@smithy/config-resolver": ^2.0.1 + "@smithy/fetch-http-handler": ^2.0.1 + "@smithy/hash-node": ^2.0.1 + "@smithy/invalid-dependency": ^2.0.1 + "@smithy/middleware-content-length": ^2.0.1 + "@smithy/middleware-endpoint": ^2.0.1 + "@smithy/middleware-retry": ^2.0.1 + "@smithy/middleware-serde": ^2.0.1 + "@smithy/middleware-stack": ^2.0.0 + "@smithy/node-config-provider": ^2.0.1 + "@smithy/node-http-handler": ^2.0.1 + "@smithy/protocol-http": ^2.0.1 + "@smithy/smithy-client": ^2.0.1 + "@smithy/types": ^2.0.2 + "@smithy/url-parser": ^2.0.1 + "@smithy/util-base64": ^2.0.0 + "@smithy/util-body-length-browser": ^2.0.0 + "@smithy/util-body-length-node": ^2.0.0 + "@smithy/util-defaults-mode-browser": ^2.0.1 + "@smithy/util-defaults-mode-node": ^2.0.1 + "@smithy/util-retry": ^2.0.0 + "@smithy/util-utf8": ^2.0.0 tslib: ^2.5.0 - checksum: fed86f95671622df777cbebede0eb63bbc59a784ea296f6c53aae2b682c26520a8f4a0d2a96068bf2366fbf10a3ca633ce3cc9fce170330022534b048d5531da + checksum: ef6c13d334913e05d3f4e0296259312beb8e257a8ed2a7421bf3239e96b8712259e8f94c12d26dc2b3b2948dfd1ea1141bde24931916c55ab398f7556e05bb6b languageName: node linkType: hard -"@aws-sdk/client-sts@npm:3.369.0": - version: 3.369.0 - resolution: "@aws-sdk/client-sts@npm:3.369.0" +"@aws-sdk/client-sts@npm:3.382.0": + version: 3.382.0 + resolution: "@aws-sdk/client-sts@npm:3.382.0" dependencies: "@aws-crypto/sha256-browser": 3.0.0 "@aws-crypto/sha256-js": 3.0.0 - "@aws-sdk/credential-provider-node": 3.369.0 - "@aws-sdk/middleware-host-header": 3.369.0 - "@aws-sdk/middleware-logger": 3.369.0 - "@aws-sdk/middleware-recursion-detection": 3.369.0 - "@aws-sdk/middleware-sdk-sts": 3.369.0 - "@aws-sdk/middleware-signing": 3.369.0 - "@aws-sdk/middleware-user-agent": 3.369.0 - "@aws-sdk/types": 3.369.0 - "@aws-sdk/util-endpoints": 3.369.0 - "@aws-sdk/util-user-agent-browser": 3.369.0 - "@aws-sdk/util-user-agent-node": 3.369.0 - "@smithy/config-resolver": ^1.0.1 - "@smithy/fetch-http-handler": ^1.0.1 - "@smithy/hash-node": ^1.0.1 - "@smithy/invalid-dependency": ^1.0.1 - "@smithy/middleware-content-length": ^1.0.1 - "@smithy/middleware-endpoint": ^1.0.1 - "@smithy/middleware-retry": ^1.0.1 - "@smithy/middleware-serde": ^1.0.1 - "@smithy/middleware-stack": ^1.0.1 - "@smithy/node-config-provider": ^1.0.1 - "@smithy/node-http-handler": ^1.0.1 - "@smithy/protocol-http": ^1.1.0 - "@smithy/smithy-client": ^1.0.2 - "@smithy/types": ^1.1.0 - "@smithy/url-parser": ^1.0.1 - "@smithy/util-base64": ^1.0.1 - "@smithy/util-body-length-browser": ^1.0.1 - "@smithy/util-body-length-node": ^1.0.1 - "@smithy/util-defaults-mode-browser": ^1.0.1 - "@smithy/util-defaults-mode-node": ^1.0.1 - "@smithy/util-retry": ^1.0.1 - "@smithy/util-utf8": ^1.0.1 + "@aws-sdk/credential-provider-node": 3.382.0 + "@aws-sdk/middleware-host-header": 3.379.1 + "@aws-sdk/middleware-logger": 3.378.0 + "@aws-sdk/middleware-recursion-detection": 3.378.0 + "@aws-sdk/middleware-sdk-sts": 3.379.1 + "@aws-sdk/middleware-signing": 3.379.1 + "@aws-sdk/middleware-user-agent": 3.382.0 + "@aws-sdk/types": 3.378.0 + "@aws-sdk/util-endpoints": 3.382.0 + "@aws-sdk/util-user-agent-browser": 3.378.0 + "@aws-sdk/util-user-agent-node": 3.378.0 + "@smithy/config-resolver": ^2.0.1 + "@smithy/fetch-http-handler": ^2.0.1 + "@smithy/hash-node": ^2.0.1 + "@smithy/invalid-dependency": ^2.0.1 + "@smithy/middleware-content-length": ^2.0.1 + "@smithy/middleware-endpoint": ^2.0.1 + "@smithy/middleware-retry": ^2.0.1 + "@smithy/middleware-serde": ^2.0.1 + "@smithy/middleware-stack": ^2.0.0 + "@smithy/node-config-provider": ^2.0.1 + "@smithy/node-http-handler": ^2.0.1 + "@smithy/protocol-http": ^2.0.1 + "@smithy/smithy-client": ^2.0.1 + "@smithy/types": ^2.0.2 + "@smithy/url-parser": ^2.0.1 + "@smithy/util-base64": ^2.0.0 + "@smithy/util-body-length-browser": ^2.0.0 + "@smithy/util-body-length-node": ^2.0.0 + "@smithy/util-defaults-mode-browser": ^2.0.1 + "@smithy/util-defaults-mode-node": ^2.0.1 + "@smithy/util-retry": ^2.0.0 + "@smithy/util-utf8": ^2.0.0 fast-xml-parser: 4.2.5 tslib: ^2.5.0 - checksum: 20ae54af825bf2b5804dcf21832a3eb450fa7be63c3f87df640902dc999e7315ddc543aa5f8801b8705b0be52ae5b5ff094f9578f7a50d9570e5001747485a18 - languageName: node - linkType: hard - -"@aws-sdk/config-resolver@npm:3.303.0": - version: 3.303.0 - resolution: "@aws-sdk/config-resolver@npm:3.303.0" - dependencies: - "@aws-sdk/types": 3.303.0 - "@aws-sdk/util-config-provider": 3.295.0 - "@aws-sdk/util-middleware": 3.303.0 - tslib: ^2.5.0 - checksum: 144dd2eafdeb618f20152067b5361a8905e290c31abdc923588cafd4ce42ee43faf6be4fbe25ba2747c4438c2086938c743e97409c942d239f14211b3c582c96 - languageName: node - linkType: hard - -"@aws-sdk/credential-provider-env@npm:3.303.0": - version: 3.303.0 - resolution: "@aws-sdk/credential-provider-env@npm:3.303.0" - dependencies: - "@aws-sdk/property-provider": 3.303.0 - "@aws-sdk/types": 3.303.0 - tslib: ^2.5.0 - checksum: afece0661ddb23f490fb026b124fd9a9f7c80dd4bbec7d8d4636f48e6bf141a09ee6845c6ae682c410261ecfcf2ce4e75bfd371acc8ea3af6291ea94fdfb0ed9 - languageName: node - linkType: hard - -"@aws-sdk/credential-provider-env@npm:3.369.0": - version: 3.369.0 - resolution: "@aws-sdk/credential-provider-env@npm:3.369.0" - dependencies: - "@aws-sdk/types": 3.369.0 - "@smithy/property-provider": ^1.0.1 - "@smithy/types": ^1.1.0 - tslib: ^2.5.0 - checksum: c9be6d1cd18f2e707290538f31ccf25ec9333ff622a66af4aaf9adad0ddf06be9136a680d620f8baa00dce83ee36660c91415da9d7662f1f8e1013434c698dc7 - languageName: node - linkType: hard - -"@aws-sdk/credential-provider-imds@npm:3.303.0": - version: 3.303.0 - resolution: "@aws-sdk/credential-provider-imds@npm:3.303.0" - dependencies: - "@aws-sdk/node-config-provider": 3.303.0 - "@aws-sdk/property-provider": 3.303.0 - "@aws-sdk/types": 3.303.0 - "@aws-sdk/url-parser": 3.303.0 - tslib: ^2.5.0 - checksum: 28ec2b23a85b3e647765d76784997771ca6669bce284d78f4718ee12edca2557f8235ed638e5ddd5ff936b0bc08917479392bbf09a3870bc9a1b3a6b1f48e8fe - languageName: node - linkType: hard - -"@aws-sdk/credential-provider-ini@npm:3.303.0": - version: 3.303.0 - resolution: "@aws-sdk/credential-provider-ini@npm:3.303.0" - dependencies: - "@aws-sdk/credential-provider-env": 3.303.0 - "@aws-sdk/credential-provider-imds": 3.303.0 - "@aws-sdk/credential-provider-process": 3.303.0 - "@aws-sdk/credential-provider-sso": 3.303.0 - "@aws-sdk/credential-provider-web-identity": 3.303.0 - "@aws-sdk/property-provider": 3.303.0 - "@aws-sdk/shared-ini-file-loader": 3.303.0 - "@aws-sdk/types": 3.303.0 - tslib: ^2.5.0 - checksum: 11695d0576309a88d0264941b3de1973a3f2a8456e2e03f0fc10601586de8553964248c90a65dbd99de1c240d60c88ab399b6e9442fb17bc9a39351d1a0b9c45 - languageName: node - linkType: hard - -"@aws-sdk/credential-provider-ini@npm:3.369.0": - version: 3.369.0 - resolution: "@aws-sdk/credential-provider-ini@npm:3.369.0" - dependencies: - "@aws-sdk/credential-provider-env": 3.369.0 - "@aws-sdk/credential-provider-process": 3.369.0 - "@aws-sdk/credential-provider-sso": 3.369.0 - "@aws-sdk/credential-provider-web-identity": 3.369.0 - "@aws-sdk/types": 3.369.0 - "@smithy/credential-provider-imds": ^1.0.1 - "@smithy/property-provider": ^1.0.1 - "@smithy/shared-ini-file-loader": ^1.0.1 - "@smithy/types": ^1.1.0 - tslib: ^2.5.0 - checksum: 9a220a58bea982a60e780dc13354d65dae668320aad929fe374a018e6b2c5d2949ad97971e2bb5466f79b451aa3f3883a2081ea15e132090df4d59f534f429fa - languageName: node - linkType: hard - -"@aws-sdk/credential-provider-node@npm:3.303.0": - version: 3.303.0 - resolution: "@aws-sdk/credential-provider-node@npm:3.303.0" - dependencies: - "@aws-sdk/credential-provider-env": 3.303.0 - "@aws-sdk/credential-provider-imds": 3.303.0 - "@aws-sdk/credential-provider-ini": 3.303.0 - "@aws-sdk/credential-provider-process": 3.303.0 - "@aws-sdk/credential-provider-sso": 3.303.0 - "@aws-sdk/credential-provider-web-identity": 3.303.0 - "@aws-sdk/property-provider": 3.303.0 - "@aws-sdk/shared-ini-file-loader": 3.303.0 - "@aws-sdk/types": 3.303.0 - tslib: ^2.5.0 - checksum: eba570e5b18e4818f00e113943984fbe523aafa1d0b035bb90de27777a2def4cb84ef70f83206c93828dda7bcb2111a6eb3ec4f5d1c5c215ff22878b0b28ab61 - languageName: node - linkType: hard - -"@aws-sdk/credential-provider-node@npm:3.369.0": - version: 3.369.0 - resolution: "@aws-sdk/credential-provider-node@npm:3.369.0" - dependencies: - "@aws-sdk/credential-provider-env": 3.369.0 - "@aws-sdk/credential-provider-ini": 3.369.0 - "@aws-sdk/credential-provider-process": 3.369.0 - "@aws-sdk/credential-provider-sso": 3.369.0 - "@aws-sdk/credential-provider-web-identity": 3.369.0 - "@aws-sdk/types": 3.369.0 - "@smithy/credential-provider-imds": ^1.0.1 - "@smithy/property-provider": ^1.0.1 - "@smithy/shared-ini-file-loader": ^1.0.1 - "@smithy/types": ^1.1.0 - tslib: ^2.5.0 - checksum: f187e2339a4e1489259bf34447c00ad4170c4a8bdfc6a55920504ce85ca9673bbd88480cdff693154ed1073941dff3663e8b93312ef47ac55698e21459316d07 - languageName: node - linkType: hard - -"@aws-sdk/credential-provider-process@npm:3.303.0": - version: 3.303.0 - resolution: "@aws-sdk/credential-provider-process@npm:3.303.0" - dependencies: - "@aws-sdk/property-provider": 3.303.0 - "@aws-sdk/shared-ini-file-loader": 3.303.0 - "@aws-sdk/types": 3.303.0 - tslib: ^2.5.0 - checksum: 7691419bf22108a87f99d365e8504e23d7afd7aec2e7c1a0f9c89e8c252a78ebd42ca3eb19a7538b590601f3183bd88d29a0f2a0ef4faa3fcf3b9ef5843a025f - languageName: node - linkType: hard - -"@aws-sdk/credential-provider-process@npm:3.369.0": - version: 3.369.0 - resolution: "@aws-sdk/credential-provider-process@npm:3.369.0" - dependencies: - "@aws-sdk/types": 3.369.0 - "@smithy/property-provider": ^1.0.1 - "@smithy/shared-ini-file-loader": ^1.0.1 - "@smithy/types": ^1.1.0 - tslib: ^2.5.0 - checksum: b11173f15c9104ffe261fdc9a4d5c180fdb1f146171f63753a7e901f4b19257d8b351fbd54d817422003be3bd21ac8358cbf642d87d4c905820849a15c6a5798 - languageName: node - linkType: hard - -"@aws-sdk/credential-provider-sso@npm:3.303.0": - version: 3.303.0 - resolution: "@aws-sdk/credential-provider-sso@npm:3.303.0" - dependencies: - "@aws-sdk/client-sso": 3.303.0 - "@aws-sdk/property-provider": 3.303.0 - "@aws-sdk/shared-ini-file-loader": 3.303.0 - "@aws-sdk/token-providers": 3.303.0 - "@aws-sdk/types": 3.303.0 - tslib: ^2.5.0 - checksum: 76825e9c83d445cf05a4078bf01dcf04ccac98cce29b06e0d3c7c0b79bc79bca58c4499298aee1b0290f92c77eaa523779ed14f2e657afcc9b80681c7e24efff - languageName: node - linkType: hard - -"@aws-sdk/credential-provider-sso@npm:3.369.0": - version: 3.369.0 - resolution: "@aws-sdk/credential-provider-sso@npm:3.369.0" - dependencies: - "@aws-sdk/client-sso": 3.369.0 - "@aws-sdk/token-providers": 3.369.0 - "@aws-sdk/types": 3.369.0 - "@smithy/property-provider": ^1.0.1 - "@smithy/shared-ini-file-loader": ^1.0.1 - "@smithy/types": ^1.1.0 - tslib: ^2.5.0 - checksum: aa7ad0f03be2d710e34a6f895bac24efbe7286b89cfeaddb3c1d75b6dcc535667160792e8c06ab4bb2aa80d67869d25d7c41c2f801f517cb4e384c3e992e0162 - languageName: node - linkType: hard - -"@aws-sdk/credential-provider-web-identity@npm:3.303.0": - version: 3.303.0 - resolution: "@aws-sdk/credential-provider-web-identity@npm:3.303.0" - dependencies: - "@aws-sdk/property-provider": 3.303.0 - "@aws-sdk/types": 3.303.0 - tslib: ^2.5.0 - checksum: 0c1a392e1de6665deebc8bd9d02b38d17590e7941af13546162481469db06733ef9d0200d6cb0ac139de6a60f211758d8d464f79547202574145b248c2fe305c - languageName: node - linkType: hard - -"@aws-sdk/credential-provider-web-identity@npm:3.369.0": - version: 3.369.0 - resolution: "@aws-sdk/credential-provider-web-identity@npm:3.369.0" - dependencies: - "@aws-sdk/types": 3.369.0 - "@smithy/property-provider": ^1.0.1 - "@smithy/types": ^1.1.0 - tslib: ^2.5.0 - checksum: 1f5da856a04cd40fe2d81f124f55ae88e5eabfe13409384e7f28f2b985866bac629444839d2c1949e62a6b76ac55ddc59f5391fb3699ed061a8b71c24297f442 - languageName: node - linkType: hard - -"@aws-sdk/fetch-http-handler@npm:3.303.0": - version: 3.303.0 - resolution: "@aws-sdk/fetch-http-handler@npm:3.303.0" - dependencies: - "@aws-sdk/protocol-http": 3.303.0 - "@aws-sdk/querystring-builder": 3.303.0 - "@aws-sdk/types": 3.303.0 - "@aws-sdk/util-base64": 3.303.0 - tslib: ^2.5.0 - checksum: 4d4606d30fd99b56ebfb05b9ac34413ed0ea889d1cb1c2f70498cce5b6e6ed40a64fd8c0ad3250a450c59d62be3d87a45399624456f2ad2768e6ba9d8e8e7e30 - languageName: node - linkType: hard - -"@aws-sdk/hash-node@npm:3.303.0": - version: 3.303.0 - resolution: "@aws-sdk/hash-node@npm:3.303.0" - dependencies: - "@aws-sdk/types": 3.303.0 - "@aws-sdk/util-buffer-from": 3.303.0 - "@aws-sdk/util-utf8": 3.303.0 - tslib: ^2.5.0 - checksum: b81ad85b30c8a532d8bb540a1f2c733b9db1615dcb59056df9e2eede071589ed8894b215182caf1d333f49087cdd9482440b400b648d16ed82ccd29efd28a8b0 - languageName: node - linkType: hard - -"@aws-sdk/invalid-dependency@npm:3.303.0": - version: 3.303.0 - resolution: "@aws-sdk/invalid-dependency@npm:3.303.0" - dependencies: - "@aws-sdk/types": 3.303.0 - tslib: ^2.5.0 - checksum: c69e42c39e015d36b7036ad3c7af38fd015380e9c8fca08cc6773529d75cb80c7267b2cd4e476c225f710f223a50ba6205fc6039d67a3d1af8482c259832e2e2 - languageName: node - linkType: hard - -"@aws-sdk/is-array-buffer@npm:3.303.0": - version: 3.303.0 - resolution: "@aws-sdk/is-array-buffer@npm:3.303.0" - dependencies: - tslib: ^2.5.0 - checksum: 3941183222030219f43c62f12db2558ae8e9d6a4c8d3798fdaf26772a255b84980d708fd81e557fd52e34e1c0945955b84e315ad39fca07ff8bdde9b68fdff00 - languageName: node - linkType: hard - -"@aws-sdk/middleware-content-length@npm:3.303.0": - version: 3.303.0 - resolution: "@aws-sdk/middleware-content-length@npm:3.303.0" - dependencies: - "@aws-sdk/protocol-http": 3.303.0 - "@aws-sdk/types": 3.303.0 - tslib: ^2.5.0 - checksum: 8214a70e40a95e276a12db31c23ded0fe3846a178883083a8fc7742510c5a4c2f1eaf59a3c9bdd22e48e7c916849776555b74f5bc8947f44de551fe85598581f - languageName: node - linkType: hard - -"@aws-sdk/middleware-endpoint@npm:3.303.0": - version: 3.303.0 - resolution: "@aws-sdk/middleware-endpoint@npm:3.303.0" - dependencies: - "@aws-sdk/middleware-serde": 3.303.0 - "@aws-sdk/types": 3.303.0 - "@aws-sdk/url-parser": 3.303.0 - "@aws-sdk/util-middleware": 3.303.0 - tslib: ^2.5.0 - checksum: 717a315640bb3d6c10f73d91e1a67cc86aa1175b0d7cb9b5a6cb92833dd99e0c38c5041ad72b3f925a65414c66039221317edf9143795e12f521ca1b3d874986 - languageName: node - linkType: hard - -"@aws-sdk/middleware-host-header@npm:3.303.0": - version: 3.303.0 - resolution: "@aws-sdk/middleware-host-header@npm:3.303.0" - dependencies: - "@aws-sdk/protocol-http": 3.303.0 - "@aws-sdk/types": 3.303.0 - tslib: ^2.5.0 - checksum: a8260c237af3063551d8bcd5a0bebe4498172cbf7e3e10e3a97771b9fa93e48284faa29776c6c7f95df466c7ecf17b4705cadff4d3d1523104451b6add17b331 - languageName: node - linkType: hard - -"@aws-sdk/middleware-host-header@npm:3.369.0": - version: 3.369.0 - resolution: "@aws-sdk/middleware-host-header@npm:3.369.0" - dependencies: - "@aws-sdk/types": 3.369.0 - "@smithy/protocol-http": ^1.1.0 - "@smithy/types": ^1.1.0 - tslib: ^2.5.0 - checksum: a490ca54f2603bb24a067bda6267d4a35e6499efbd35c0394ed31a381c01907ec989043b5ead949e99e5bdb2aff39c465ba34a29aec00e261efc4155984575a7 - languageName: node - linkType: hard - -"@aws-sdk/middleware-logger@npm:3.303.0": - version: 3.303.0 - resolution: "@aws-sdk/middleware-logger@npm:3.303.0" - dependencies: - "@aws-sdk/types": 3.303.0 - tslib: ^2.5.0 - checksum: a12873585dd6b952a63e635e4900a7f699c298783289787885a57d23000db15f31b1a3bc37209fab374b035b6f9e85df555d366189496e4c9b62731c56694c85 - languageName: node - linkType: hard - -"@aws-sdk/middleware-logger@npm:3.369.0": - version: 3.369.0 - resolution: "@aws-sdk/middleware-logger@npm:3.369.0" - dependencies: - "@aws-sdk/types": 3.369.0 - "@smithy/types": ^1.1.0 - tslib: ^2.5.0 - checksum: 9228d8af36191e1cdcb5bb8f97d961e42f9324e1da196d2df55b90fc76fd3731e698d611c5fb465da7d0bf3a5cd2940390a2abd99d956710b9acf27b0a2c62e7 - languageName: node - linkType: hard - -"@aws-sdk/middleware-recursion-detection@npm:3.303.0": - version: 3.303.0 - resolution: "@aws-sdk/middleware-recursion-detection@npm:3.303.0" - dependencies: - "@aws-sdk/protocol-http": 3.303.0 - "@aws-sdk/types": 3.303.0 - tslib: ^2.5.0 - checksum: 175b3197fcc93a70d0ff5229e5802b33f55ab7b26b5f6303a75cb62a4856a30c413bf99d5139a2c22f465f1a4d77c28b399839f8151595af96191fe2cc612b40 - languageName: node - linkType: hard - -"@aws-sdk/middleware-recursion-detection@npm:3.369.0": - version: 3.369.0 - resolution: "@aws-sdk/middleware-recursion-detection@npm:3.369.0" - dependencies: - "@aws-sdk/types": 3.369.0 - "@smithy/protocol-http": ^1.1.0 - "@smithy/types": ^1.1.0 - tslib: ^2.5.0 - checksum: 2e9e634d30c873d61a75e2435d4dace6a58e27cc407ca79089b7b996ac5a42f38d733b8515bfbe82b75f165e8b4a461469b46d3acecbea98fb0d1b5c7eaf40a2 - languageName: node - linkType: hard - -"@aws-sdk/middleware-retry@npm:3.303.0": - version: 3.303.0 - resolution: "@aws-sdk/middleware-retry@npm:3.303.0" - dependencies: - "@aws-sdk/protocol-http": 3.303.0 - "@aws-sdk/service-error-classification": 3.303.0 - "@aws-sdk/types": 3.303.0 - "@aws-sdk/util-middleware": 3.303.0 - "@aws-sdk/util-retry": 3.303.0 - tslib: ^2.5.0 - uuid: ^8.3.2 - checksum: f050963db40894413608cc3f02f3bed7008d67e981d756bade6ef47ca2b27fb66a408d3e86d6ce93cc4cec1e3a7929696ce33cf7e5433c3560cf5ebfd23ae75c - languageName: node - linkType: hard - -"@aws-sdk/middleware-sdk-sts@npm:3.303.0": - version: 3.303.0 - resolution: "@aws-sdk/middleware-sdk-sts@npm:3.303.0" - dependencies: - "@aws-sdk/middleware-signing": 3.303.0 - "@aws-sdk/types": 3.303.0 - tslib: ^2.5.0 - checksum: d5499225e8a38d8961240d0d9aef1079dd0b4c00cbd6508330e801ab0c791863b29a15c482904d51bb8f454b4dca6da636e1b403d1e4f0f767223d8edf97bb38 - languageName: node - linkType: hard - -"@aws-sdk/middleware-sdk-sts@npm:3.369.0": - version: 3.369.0 - resolution: "@aws-sdk/middleware-sdk-sts@npm:3.369.0" - dependencies: - "@aws-sdk/middleware-signing": 3.369.0 - "@aws-sdk/types": 3.369.0 - "@smithy/types": ^1.1.0 - tslib: ^2.5.0 - checksum: 24af557b042bd5f1952885036f53256c04067d40092e53895af5f90223a9838ee50cb39cc8491b7623778d77dd164460bd4cc08de2c4b78183ea84e94a7b5a8a - languageName: node - linkType: hard - -"@aws-sdk/middleware-serde@npm:3.303.0": - version: 3.303.0 - resolution: "@aws-sdk/middleware-serde@npm:3.303.0" - dependencies: - "@aws-sdk/types": 3.303.0 - tslib: ^2.5.0 - checksum: 43cfab83269476dfa228cf682bd7d07895ee1c30c628a440b23eba441841ff1863aa300fc0b6dabe8fe2f364b37eab49975bb3ccdc9b8b7e09682e081ac8323b - languageName: node - linkType: hard - -"@aws-sdk/middleware-signing@npm:3.303.0": - version: 3.303.0 - resolution: "@aws-sdk/middleware-signing@npm:3.303.0" - dependencies: - "@aws-sdk/property-provider": 3.303.0 - "@aws-sdk/protocol-http": 3.303.0 - "@aws-sdk/signature-v4": 3.303.0 - "@aws-sdk/types": 3.303.0 - "@aws-sdk/util-middleware": 3.303.0 - tslib: ^2.5.0 - checksum: 004dee6ec54cb0169dad72bf2cabe7408ae46dec4d114abb9f806662da8a4a87bc2dc2ab1a7434794d1142f6d708a40f1b6b941aa38eb7e55bb29d743dd48f55 - languageName: node - linkType: hard - -"@aws-sdk/middleware-signing@npm:3.369.0": - version: 3.369.0 - resolution: "@aws-sdk/middleware-signing@npm:3.369.0" - dependencies: - "@aws-sdk/types": 3.369.0 - "@smithy/property-provider": ^1.0.1 - "@smithy/protocol-http": ^1.1.0 - "@smithy/signature-v4": ^1.0.1 - "@smithy/types": ^1.1.0 - "@smithy/util-middleware": ^1.0.1 - tslib: ^2.5.0 - checksum: 5d18cde4623f5ffa6393cf08f76a8091ae45a2ac2f9de4eac4dbcac69b8b634d7d0079950c3be69cccd1c6021c108265adac844b554d91771f700f14bbebb7b0 - languageName: node - linkType: hard - -"@aws-sdk/middleware-stack@npm:3.303.0": - version: 3.303.0 - resolution: "@aws-sdk/middleware-stack@npm:3.303.0" - dependencies: - tslib: ^2.5.0 - checksum: 55740ba2780547df550a6b6623a6a75c4487e6a0f3009daca03f22cc9344f1caa2b3ab8861076cec92946339525c2a983c883aae201b97087b79af74b38ff610 - languageName: node - linkType: hard - -"@aws-sdk/middleware-user-agent@npm:3.303.0": - version: 3.303.0 - resolution: "@aws-sdk/middleware-user-agent@npm:3.303.0" - dependencies: - "@aws-sdk/protocol-http": 3.303.0 - "@aws-sdk/types": 3.303.0 - "@aws-sdk/util-endpoints": 3.303.0 - tslib: ^2.5.0 - checksum: 57a901a7e36dad89c1c7dd0af397975103a62b2687651e5e899f392b224e686890581ca23c4bf08ea363d3f296d62c0cd897ea6fc84d0a3b11a1c76901346f1e - languageName: node - linkType: hard - -"@aws-sdk/middleware-user-agent@npm:3.369.0": - version: 3.369.0 - resolution: "@aws-sdk/middleware-user-agent@npm:3.369.0" - dependencies: - "@aws-sdk/types": 3.369.0 - "@aws-sdk/util-endpoints": 3.369.0 - "@smithy/protocol-http": ^1.1.0 - "@smithy/types": ^1.1.0 - tslib: ^2.5.0 - checksum: 9b0846e32faedcc2775ad7101ffe5fec75a0bd1550cb3271b7942c51f88e941509dbb6f030828a6b90aceec6021b8f79e075a543476b595fa57a8fc613fe4b0f - languageName: node - linkType: hard - -"@aws-sdk/node-config-provider@npm:3.303.0": - version: 3.303.0 - resolution: "@aws-sdk/node-config-provider@npm:3.303.0" - dependencies: - "@aws-sdk/property-provider": 3.303.0 - "@aws-sdk/shared-ini-file-loader": 3.303.0 - "@aws-sdk/types": 3.303.0 - tslib: ^2.5.0 - checksum: ac8cecab9fb8249f00fa3695b8f7d41f0b4eae3fb6954f736631165504d59a556f50fca3bfd0eb52eedfb910f6b67a86519d87ef29589b5dabf4781d1351478a - languageName: node - linkType: hard - -"@aws-sdk/node-http-handler@npm:3.303.0": - version: 3.303.0 - resolution: "@aws-sdk/node-http-handler@npm:3.303.0" - dependencies: - "@aws-sdk/abort-controller": 3.303.0 - "@aws-sdk/protocol-http": 3.303.0 - "@aws-sdk/querystring-builder": 3.303.0 - "@aws-sdk/types": 3.303.0 - tslib: ^2.5.0 - checksum: 828047c451aa3964b1be43486c026a3393540abc6b68e82b79c168521a78e6dc65c50bbdc02c5af81c07c98b4e015d203069d02e3b037dc25f9d3fd6ce6a64d8 - languageName: node - linkType: hard - -"@aws-sdk/property-provider@npm:3.303.0": - version: 3.303.0 - resolution: "@aws-sdk/property-provider@npm:3.303.0" - dependencies: - "@aws-sdk/types": 3.303.0 - tslib: ^2.5.0 - checksum: 5eb280802332b2f7fc62fecabc54df6cc1d00dc16f9c22c5209e4908becc1ff560a806e908ab5a111d06351b1ee25d15b4fb0d7167692507f3975b1fdc60cf5f - languageName: node - linkType: hard - -"@aws-sdk/protocol-http@npm:3.303.0": - version: 3.303.0 - resolution: "@aws-sdk/protocol-http@npm:3.303.0" - dependencies: - "@aws-sdk/types": 3.303.0 - tslib: ^2.5.0 - checksum: d91ee8df751b964c6591f22bc14180032c9872316dc11a0e149a6fb03fe1a74c1fb5476e1d6909bfec7e06e8c219d6fae640bc00664fc08c51beabfcc0cadde0 - languageName: node - linkType: hard - -"@aws-sdk/querystring-builder@npm:3.303.0": - version: 3.303.0 - resolution: "@aws-sdk/querystring-builder@npm:3.303.0" - dependencies: - "@aws-sdk/types": 3.303.0 - "@aws-sdk/util-uri-escape": 3.303.0 - tslib: ^2.5.0 - checksum: 9745d8ab11c590a16ae633a551ed5b7227e7e434a0c06753b2636cb3c58618412ec78b349f9070f7727375e237b0f012b56d956a0cb4c86b569bc97ee34eb7ce - languageName: node - linkType: hard - -"@aws-sdk/querystring-parser@npm:3.303.0": - version: 3.303.0 - resolution: "@aws-sdk/querystring-parser@npm:3.303.0" - dependencies: - "@aws-sdk/types": 3.303.0 - tslib: ^2.5.0 - checksum: cb6f87d322475eb0e72146b3f875558a29b54f7e30bdec2303e57177ecea372b76d02dc81f8e886ca87e0e157d19b66d94ff724aa4c93f43d597cebd25c51c6d - languageName: node - linkType: hard - -"@aws-sdk/service-error-classification@npm:3.303.0": - version: 3.303.0 - resolution: "@aws-sdk/service-error-classification@npm:3.303.0" - checksum: c1a4c4e0dd27cb34106bffe45ceec2b686208c26b1fc79161dff6ac691cf198a1becf667da2d548c1aa8302c9aec11355df519a2b067ad461060c0a406e699b4 - languageName: node - linkType: hard - -"@aws-sdk/shared-ini-file-loader@npm:3.303.0": - version: 3.303.0 - resolution: "@aws-sdk/shared-ini-file-loader@npm:3.303.0" - dependencies: - "@aws-sdk/types": 3.303.0 - tslib: ^2.5.0 - checksum: cd806e5813664eae97941bda8a9d101fca924b57a54f7a1187a4332fcebcf767839ac4e04157edc2a7129a7e1b537e6ff0912a834c88468893c6c8c941480844 + checksum: e13fe8cfc271ea8fb56718e3e844a9715960165b65f6f8b226f02859d34344f1380987a4ce808530444c78853033ec37048dd4fa9c93ac0df8de89eeba71bd8e languageName: node linkType: hard -"@aws-sdk/signature-v4@npm:3.303.0": - version: 3.303.0 - resolution: "@aws-sdk/signature-v4@npm:3.303.0" +"@aws-sdk/credential-provider-env@npm:3.378.0": + version: 3.378.0 + resolution: "@aws-sdk/credential-provider-env@npm:3.378.0" dependencies: - "@aws-sdk/is-array-buffer": 3.303.0 - "@aws-sdk/types": 3.303.0 - "@aws-sdk/util-hex-encoding": 3.295.0 - "@aws-sdk/util-middleware": 3.303.0 - "@aws-sdk/util-uri-escape": 3.303.0 - "@aws-sdk/util-utf8": 3.303.0 + "@aws-sdk/types": 3.378.0 + "@smithy/property-provider": ^2.0.0 + "@smithy/types": ^2.0.2 tslib: ^2.5.0 - checksum: 5183220ce2af45e187a8ced6935392cb6be7383ec5415bbfdba594df9963abbd1ff0ceab6c14f724ca960a7640d055275e6eeb535e173a4683f1a8c68ea0210c + checksum: ff32e595305f93756bb8fe9b26ca9a5336ca0517ca0d62dfc4cfa36d3bb990a65ebe7b0afcebaf102e4037ddb5b03132a6dde268f421bb72cbbd4249a22715b6 languageName: node linkType: hard -"@aws-sdk/smithy-client@npm:3.303.0": - version: 3.303.0 - resolution: "@aws-sdk/smithy-client@npm:3.303.0" +"@aws-sdk/credential-provider-ini@npm:3.382.0": + version: 3.382.0 + resolution: "@aws-sdk/credential-provider-ini@npm:3.382.0" dependencies: - "@aws-sdk/middleware-stack": 3.303.0 - "@aws-sdk/types": 3.303.0 + "@aws-sdk/credential-provider-env": 3.378.0 + "@aws-sdk/credential-provider-process": 3.378.0 + "@aws-sdk/credential-provider-sso": 3.382.0 + "@aws-sdk/credential-provider-web-identity": 3.378.0 + "@aws-sdk/types": 3.378.0 + "@smithy/credential-provider-imds": ^2.0.0 + "@smithy/property-provider": ^2.0.0 + "@smithy/shared-ini-file-loader": ^2.0.0 + "@smithy/types": ^2.0.2 tslib: ^2.5.0 - checksum: 6812280c41acae85531c78484ce162c58c3e843f6bb04f8b3dafd6551f60e57be5e6fbd78f2cdafaf5a94d5cd0d3f0af4d3e705a237c1183d720e1be81d40989 + checksum: c52c431f03864d46d23e3515516c62a91af175995fba9dadb7cff4e005d7d2ed19b9b5508859768ce4d98746da260709e7caf703c7211b1a21c62a6a1606ef5d languageName: node linkType: hard -"@aws-sdk/token-providers@npm:3.303.0": - version: 3.303.0 - resolution: "@aws-sdk/token-providers@npm:3.303.0" +"@aws-sdk/credential-provider-node@npm:3.382.0": + version: 3.382.0 + resolution: "@aws-sdk/credential-provider-node@npm:3.382.0" dependencies: - "@aws-sdk/client-sso-oidc": 3.303.0 - "@aws-sdk/property-provider": 3.303.0 - "@aws-sdk/shared-ini-file-loader": 3.303.0 - "@aws-sdk/types": 3.303.0 + "@aws-sdk/credential-provider-env": 3.378.0 + "@aws-sdk/credential-provider-ini": 3.382.0 + "@aws-sdk/credential-provider-process": 3.378.0 + "@aws-sdk/credential-provider-sso": 3.382.0 + "@aws-sdk/credential-provider-web-identity": 3.378.0 + "@aws-sdk/types": 3.378.0 + "@smithy/credential-provider-imds": ^2.0.0 + "@smithy/property-provider": ^2.0.0 + "@smithy/shared-ini-file-loader": ^2.0.0 + "@smithy/types": ^2.0.2 tslib: ^2.5.0 - checksum: c67c55e0e2caecc4620aea3a12d13a0fff7c306f3acddc4a96b13762d7e65dc154a4de50b98db4b497196cf1122810a577dcaa58592be1d8fb73e591277b59cc + checksum: 895405b84fc938b0c3e34f73c63b5426761de3bc2dcfa06e1a06f55ae0f8135d81530c0340c0b1b33b4bcb479f5048a9ec0390288503a261c692a0df2a0b933d languageName: node linkType: hard -"@aws-sdk/token-providers@npm:3.369.0": - version: 3.369.0 - resolution: "@aws-sdk/token-providers@npm:3.369.0" +"@aws-sdk/credential-provider-process@npm:3.378.0": + version: 3.378.0 + resolution: "@aws-sdk/credential-provider-process@npm:3.378.0" dependencies: - "@aws-sdk/client-sso-oidc": 3.369.0 - "@aws-sdk/types": 3.369.0 - "@smithy/property-provider": ^1.0.1 - "@smithy/shared-ini-file-loader": ^1.0.1 - "@smithy/types": ^1.1.0 + "@aws-sdk/types": 3.378.0 + "@smithy/property-provider": ^2.0.0 + "@smithy/shared-ini-file-loader": ^2.0.0 + "@smithy/types": ^2.0.2 tslib: ^2.5.0 - checksum: e0e7aa4eacdf48d97704d3720c675494fcb3584d0ee05528e770ac4e508cfbfcff3d707b489d4b71c1cabfa020cb2ec6c3c3c5253128788ed94f9c02de66d22a + checksum: 6b8343fb9266ef490bf8e654f0d0b53585498ecf755e0e5cbee9bc93426927703637d02740050c491738a83c097a4d848244f3a66a633fd526d9339d846bdbdd languageName: node linkType: hard -"@aws-sdk/types@npm:3.303.0, @aws-sdk/types@npm:^3.222.0": - version: 3.303.0 - resolution: "@aws-sdk/types@npm:3.303.0" +"@aws-sdk/credential-provider-sso@npm:3.382.0": + version: 3.382.0 + resolution: "@aws-sdk/credential-provider-sso@npm:3.382.0" dependencies: + "@aws-sdk/client-sso": 3.382.0 + "@aws-sdk/token-providers": 3.382.0 + "@aws-sdk/types": 3.378.0 + "@smithy/property-provider": ^2.0.0 + "@smithy/shared-ini-file-loader": ^2.0.0 + "@smithy/types": ^2.0.2 tslib: ^2.5.0 - checksum: 56765a98dfd70309b3e07c86ae2ad44b1ead064547a3cb06ebb04033cceaf5626eaedacfc986234042d6aff5b09450c39f2d56b1d50031f7e7a369047e30d8ff + checksum: ec0d216077a0dd5724fed30cdbc4a235b998c5b5821ba250be749e0e4c0410ce1e06c1a2531281570798b77fbe7a9ff36a0d9960853d8f6b2d98247702649d68 languageName: node linkType: hard -"@aws-sdk/types@npm:3.369.0": - version: 3.369.0 - resolution: "@aws-sdk/types@npm:3.369.0" +"@aws-sdk/credential-provider-web-identity@npm:3.378.0": + version: 3.378.0 + resolution: "@aws-sdk/credential-provider-web-identity@npm:3.378.0" dependencies: - "@smithy/types": 1.1.0 + "@aws-sdk/types": 3.378.0 + "@smithy/property-provider": ^2.0.0 + "@smithy/types": ^2.0.2 tslib: ^2.5.0 - checksum: 41da67a3b9c1a09b641c0dcb81cdfd4bd441c8b91ec1e31fdc453314391785d6f7c48ac3629ce80258533d63cc621982b01b0985ce9dd8c25abb732b43fd8a9a + checksum: 22cf227f609ea654749517e25c88e9c5e7b9dd97cc781d168b1f7cedf26a692a0d1643c3933a1975a95414c0d6a7dbcef6255ca4f13a788e101f95372e17a3af languageName: node linkType: hard -"@aws-sdk/url-parser@npm:3.303.0": - version: 3.303.0 - resolution: "@aws-sdk/url-parser@npm:3.303.0" +"@aws-sdk/middleware-host-header@npm:3.379.1": + version: 3.379.1 + resolution: "@aws-sdk/middleware-host-header@npm:3.379.1" dependencies: - "@aws-sdk/querystring-parser": 3.303.0 - "@aws-sdk/types": 3.303.0 + "@aws-sdk/types": 3.378.0 + "@smithy/protocol-http": ^2.0.1 + "@smithy/types": ^2.0.2 tslib: ^2.5.0 - checksum: 1d4fe2bbdc8b3f52851c8cb4280795e804e74442999af6cffbefe61420c8fe0ac3970ec25afe7fba5db3a83f1c429f356f5c7c51bdb40b79ca3aa0195ca0dccb + checksum: 32651531108e43edf745721828feab703ab7f39837da8214bb37593200daab71042336a25b58bd9b3e9b78ac647a828674a48ebbc1f5946a533f8642fb16b92d languageName: node linkType: hard -"@aws-sdk/util-base64@npm:3.303.0": - version: 3.303.0 - resolution: "@aws-sdk/util-base64@npm:3.303.0" +"@aws-sdk/middleware-logger@npm:3.378.0": + version: 3.378.0 + resolution: "@aws-sdk/middleware-logger@npm:3.378.0" dependencies: - "@aws-sdk/util-buffer-from": 3.303.0 + "@aws-sdk/types": 3.378.0 + "@smithy/types": ^2.0.2 tslib: ^2.5.0 - checksum: 6fc1b59ff5fb33b50764eb52aa440b1da0ed066d57b24b5e53b69d252f0854ac5c3d16cbf73abfa75f39407994da4500f39bb06284a3fbc1eedeba87b0ffd78e + checksum: c1c32a5ba4ae92cf988d2c0fed3a15975e1b4612cb546133ac74eb91276fc0714278e3aa30e88d0fae00596208baed76b58e97d952daafd63777b79f185a6c3f languageName: node linkType: hard -"@aws-sdk/util-body-length-browser@npm:3.303.0": - version: 3.303.0 - resolution: "@aws-sdk/util-body-length-browser@npm:3.303.0" +"@aws-sdk/middleware-recursion-detection@npm:3.378.0": + version: 3.378.0 + resolution: "@aws-sdk/middleware-recursion-detection@npm:3.378.0" dependencies: + "@aws-sdk/types": 3.378.0 + "@smithy/protocol-http": ^2.0.1 + "@smithy/types": ^2.0.2 tslib: ^2.5.0 - checksum: cb4acab52b5f415b7d319edb6a190b2e767b595a3e5994b78e50c9583a2734238b0d77119e39a6f1072f7a58924e4e94baa7bbb722dca0fc13c3d6d2e0586d7e + checksum: 2104b3834ac6674afac7a50eb4430277ef789f3fbf439d2adab9d43b6dca5dad7518f2a8cb578d3f2ff2c9883ee4e756c7fa10166c0e49f8ea307555f69b06f0 languageName: node linkType: hard -"@aws-sdk/util-body-length-node@npm:3.303.0": - version: 3.303.0 - resolution: "@aws-sdk/util-body-length-node@npm:3.303.0" +"@aws-sdk/middleware-sdk-sts@npm:3.379.1": + version: 3.379.1 + resolution: "@aws-sdk/middleware-sdk-sts@npm:3.379.1" dependencies: + "@aws-sdk/middleware-signing": 3.379.1 + "@aws-sdk/types": 3.378.0 + "@smithy/types": ^2.0.2 tslib: ^2.5.0 - checksum: 0fe92b9a1bec82dcfd917bdc7f27c41886e2d6823dafbb2514ff117e736986d72d934f81e9cf892b27755de3e1f4f37d600eba9d78eaeebdb0fda4c1f882eae4 + checksum: 22fd2f565c5270ca0de75fbd7d751e15639a8d5fb75656bd8caf62091eda8e47ea208514964a1ae211db63b4d7e98d9a8a8f6e4067953e18a74c039e1a8b9084 languageName: node linkType: hard -"@aws-sdk/util-buffer-from@npm:3.303.0": - version: 3.303.0 - resolution: "@aws-sdk/util-buffer-from@npm:3.303.0" +"@aws-sdk/middleware-signing@npm:3.379.1": + version: 3.379.1 + resolution: "@aws-sdk/middleware-signing@npm:3.379.1" dependencies: - "@aws-sdk/is-array-buffer": 3.303.0 + "@aws-sdk/types": 3.378.0 + "@smithy/property-provider": ^2.0.0 + "@smithy/protocol-http": ^2.0.1 + "@smithy/signature-v4": ^2.0.0 + "@smithy/types": ^2.0.2 + "@smithy/util-middleware": ^2.0.0 tslib: ^2.5.0 - checksum: b2b37f61415633712499d4577497ec822c8c64a261a199db88d307084b54e42a9e1105c15e7cef4e1c7670ce8c00706fffb3b447b62be9eb75ec7a13efb009c9 + checksum: f36f6de13624f504f14db8039e02a6473d366eb11c670f555613ddeb0feac523d08f7d66a985c2a7c1eee548c66f55ac4ed7c148e79efe3fa1f5ad165af627f4 languageName: node linkType: hard -"@aws-sdk/util-config-provider@npm:3.295.0": - version: 3.295.0 - resolution: "@aws-sdk/util-config-provider@npm:3.295.0" +"@aws-sdk/middleware-user-agent@npm:3.382.0": + version: 3.382.0 + resolution: "@aws-sdk/middleware-user-agent@npm:3.382.0" dependencies: + "@aws-sdk/types": 3.378.0 + "@aws-sdk/util-endpoints": 3.382.0 + "@smithy/protocol-http": ^2.0.1 + "@smithy/types": ^2.0.2 tslib: ^2.5.0 - checksum: 2b6cd2b118465a36c9908dfc330f9f107a0a67cbde2293101af8b3ca0cb2d9f29f76394261880535d62da74b10cf89c5433a2d4524272d5b8776ad8085b0489b + checksum: e53150ecc07f408caa6fa69e6e30ebf9aa000fb59737eeb350fdf4db4789e0d2163bf14f17f4ff4223174e932ce5b71bd9ec662efc61a78a963e12891ecfed6f languageName: node linkType: hard -"@aws-sdk/util-defaults-mode-browser@npm:3.303.0": - version: 3.303.0 - resolution: "@aws-sdk/util-defaults-mode-browser@npm:3.303.0" +"@aws-sdk/token-providers@npm:3.382.0": + version: 3.382.0 + resolution: "@aws-sdk/token-providers@npm:3.382.0" dependencies: - "@aws-sdk/property-provider": 3.303.0 - "@aws-sdk/types": 3.303.0 - bowser: ^2.11.0 + "@aws-sdk/client-sso-oidc": 3.382.0 + "@aws-sdk/types": 3.378.0 + "@smithy/property-provider": ^2.0.0 + "@smithy/shared-ini-file-loader": ^2.0.0 + "@smithy/types": ^2.0.2 tslib: ^2.5.0 - checksum: 103ee291aa228161bb5112742bda4e6676e540e286bbb7a492d49c7582d84eb0567459c9d6c93252a8ceb7d8a8b666812c41926e7416d87fe69f62dcd0589e8f + checksum: 813dc973f616c2d7ec6f71da14e92d7c4b84c28309ac4d8426fe88d0c6bbe70006f9df0a984985cfcf232976188afc3d17ac5168e832e717ae4db41a21c6d67d languageName: node linkType: hard -"@aws-sdk/util-defaults-mode-node@npm:3.303.0": - version: 3.303.0 - resolution: "@aws-sdk/util-defaults-mode-node@npm:3.303.0" +"@aws-sdk/types@npm:3.378.0, @aws-sdk/types@npm:^3.222.0": + version: 3.378.0 + resolution: "@aws-sdk/types@npm:3.378.0" dependencies: - "@aws-sdk/config-resolver": 3.303.0 - "@aws-sdk/credential-provider-imds": 3.303.0 - "@aws-sdk/node-config-provider": 3.303.0 - "@aws-sdk/property-provider": 3.303.0 - "@aws-sdk/types": 3.303.0 + "@smithy/types": ^2.0.2 tslib: ^2.5.0 - checksum: 268ea6971923b90e4c1d41c2466c23b38180876c1867a69820824ff5c26524a1cab17ab57d03fa6d75e1f8714e31e47e61d785dcc0b9b4e591234ea4f7e6be96 + checksum: c4c7ebb48a625cb990a1288466f2dd8f0d770078cc77b60d5ee4a803b473ff41df474271dff26d3dadad151d5a016b398167738dd4926266ff1cd04585d4d8e8 languageName: node linkType: hard -"@aws-sdk/util-endpoints@npm:3.303.0": - version: 3.303.0 - resolution: "@aws-sdk/util-endpoints@npm:3.303.0" +"@aws-sdk/util-endpoints@npm:3.382.0": + version: 3.382.0 + resolution: "@aws-sdk/util-endpoints@npm:3.382.0" dependencies: - "@aws-sdk/types": 3.303.0 + "@aws-sdk/types": 3.378.0 tslib: ^2.5.0 - checksum: 4eeee6bae6e3d4b755164d58f7105fe3311b99781dafef04d6dc3f0b42898db698abbb290b4d15d26bfd9f75abb9b420bab203fee86939f79c8d482e37d5ae70 - languageName: node - linkType: hard - -"@aws-sdk/util-endpoints@npm:3.369.0": - version: 3.369.0 - resolution: "@aws-sdk/util-endpoints@npm:3.369.0" - dependencies: - "@aws-sdk/types": 3.369.0 - tslib: ^2.5.0 - checksum: d15363ebd66d8bb58452bda1577b787398b79761e88eae9310d31eabb5233db1c6411ab3ab2e3596c7f33081e4cb632acef6d9a16353c0f3ffabc343b4ce3575 - languageName: node - linkType: hard - -"@aws-sdk/util-hex-encoding@npm:3.295.0": - version: 3.295.0 - resolution: "@aws-sdk/util-hex-encoding@npm:3.295.0" - dependencies: - tslib: ^2.5.0 - checksum: 4b85f087de5c2a8317ff13df4947e355b4c4acae1dd283133e53139457252fb83951194c85e07b89a1e12cecec1b3c0dbd11b7d0f9f2a7775d8c6d3d9c21371e + checksum: 9573e0d30ef7aafb5f51a51d7cf0416b8fe21af6456bff2c1e660a615325012177b70bf78f6591dbb6d863f4e57ac6ac3279bfa3eca2d504a9121f82edf73118 languageName: node linkType: hard "@aws-sdk/util-locate-window@npm:^3.0.0": - version: 3.295.0 - resolution: "@aws-sdk/util-locate-window@npm:3.295.0" + version: 3.310.0 + resolution: "@aws-sdk/util-locate-window@npm:3.310.0" dependencies: tslib: ^2.5.0 - checksum: 81546c251a4f58915059d9d46b207f90ce44890e48fd87507ca280d70a719d8f963864afcffc676fd10ffa55e9b272fc5a522bd0e3d6dde379739adb5b429501 + checksum: d552ce5f0f836ecb13d7920ae650552c56706f26a5e8abf894ba471e18775a3791869bda95269153735bac9d211efc3ba78ea01c34428c3fed4318ac693a08bc languageName: node linkType: hard -"@aws-sdk/util-middleware@npm:3.303.0": - version: 3.303.0 - resolution: "@aws-sdk/util-middleware@npm:3.303.0" +"@aws-sdk/util-user-agent-browser@npm:3.378.0": + version: 3.378.0 + resolution: "@aws-sdk/util-user-agent-browser@npm:3.378.0" dependencies: - tslib: ^2.5.0 - checksum: 588f14ea42162d8d8ce57a2c5aa39d69f53ce0208885552e5af2f880d833210bf06d5068739a14794da0c9241f329fd6a76bf8237e78a240d957a9ce05037192 - languageName: node - linkType: hard - -"@aws-sdk/util-retry@npm:3.303.0": - version: 3.303.0 - resolution: "@aws-sdk/util-retry@npm:3.303.0" - dependencies: - "@aws-sdk/service-error-classification": 3.303.0 - tslib: ^2.5.0 - checksum: b5d797a9a943b8ceec3d01a183fb280ca5fea66107bd4700f1e706a6492a13d5ff93c1d5e310f9b6c862d8b341798d137107a78db5669b4c6efaf314ef7aca79 - languageName: node - linkType: hard - -"@aws-sdk/util-uri-escape@npm:3.303.0": - version: 3.303.0 - resolution: "@aws-sdk/util-uri-escape@npm:3.303.0" - dependencies: - tslib: ^2.5.0 - checksum: 45b7897f26f1ad0da8e890512336bf8221e3ac34dab406669a7ef0ae7251611ef7d373bb39ff9b3e0e4e22c5a0297c950fba3ac0361d879742fe5629909df823 - languageName: node - linkType: hard - -"@aws-sdk/util-user-agent-browser@npm:3.303.0": - version: 3.303.0 - resolution: "@aws-sdk/util-user-agent-browser@npm:3.303.0" - dependencies: - "@aws-sdk/types": 3.303.0 + "@aws-sdk/types": 3.378.0 + "@smithy/types": ^2.0.2 bowser: ^2.11.0 tslib: ^2.5.0 - checksum: 3f1e3038bbc74d27fa90cc976d34928a0b933b5bcbc6999f47fee5a7652b06332ac843554f3ff2ab486376826319b13368f5523d131ccdcd0bd80deb21c4d976 + checksum: 412ef9ffdcd3d32b9e9bb62dfa252871bc9fbee19814553aeff65bffb21de6ca236199b47eb646c4af8cb326aebab3d63ef4b303050e61fb08eed0d12bb73f8c languageName: node linkType: hard -"@aws-sdk/util-user-agent-browser@npm:3.369.0": - version: 3.369.0 - resolution: "@aws-sdk/util-user-agent-browser@npm:3.369.0" +"@aws-sdk/util-user-agent-node@npm:3.378.0": + version: 3.378.0 + resolution: "@aws-sdk/util-user-agent-node@npm:3.378.0" dependencies: - "@aws-sdk/types": 3.369.0 - "@smithy/types": ^1.1.0 - bowser: ^2.11.0 - tslib: ^2.5.0 - checksum: cffbb9d918d9150a9c28c4a83fcdcf7b632d058bb1f95250ba94f6df5c66b21c6ad826fdf57a0ce0006c57be57813ebac07e41e2a576a31cf17ade98c5ad0053 - languageName: node - linkType: hard - -"@aws-sdk/util-user-agent-node@npm:3.303.0": - version: 3.303.0 - resolution: "@aws-sdk/util-user-agent-node@npm:3.303.0" - dependencies: - "@aws-sdk/node-config-provider": 3.303.0 - "@aws-sdk/types": 3.303.0 + "@aws-sdk/types": 3.378.0 + "@smithy/node-config-provider": ^2.0.1 + "@smithy/types": ^2.0.2 tslib: ^2.5.0 peerDependencies: aws-crt: ">=1.0.0" peerDependenciesMeta: aws-crt: optional: true - checksum: 1a58a39c8d71556d414a802e5b23fa1632eb92425c38cc90630b4dd1703c4c675ffe04de80bfc51e9efd9f777e745bc3620a4172a612f62faf33c2574ce3c48e - languageName: node - linkType: hard - -"@aws-sdk/util-user-agent-node@npm:3.369.0": - version: 3.369.0 - resolution: "@aws-sdk/util-user-agent-node@npm:3.369.0" - dependencies: - "@aws-sdk/types": 3.369.0 - "@smithy/node-config-provider": ^1.0.1 - "@smithy/types": ^1.1.0 - tslib: ^2.5.0 - peerDependencies: - aws-crt: ">=1.0.0" - peerDependenciesMeta: - aws-crt: - optional: true - checksum: 5f0d43d4861207fbc684e8081e3d352ba53c573cf71e18ece5f9d78e1a37170b485a0fd2a1e16b22205a20f23bb18ed12549508b5e61bd35996a3e80c330e68a + checksum: b37271d56a8d91072b7af5d71351e388a822b389167638d779ce0b099a1024b412fd3b50a6b6334687a3ae0bebec89479e48a9dceed2262f8801e76e3b5f89ad languageName: node linkType: hard @@ -1255,29 +570,19 @@ __metadata: languageName: node linkType: hard -"@aws-sdk/util-utf8@npm:3.303.0": - version: 3.303.0 - resolution: "@aws-sdk/util-utf8@npm:3.303.0" +"@babel/code-frame@npm:^7.0.0, @babel/code-frame@npm:^7.16.7, @babel/code-frame@npm:^7.22.5": + version: 7.22.5 + resolution: "@babel/code-frame@npm:7.22.5" dependencies: - "@aws-sdk/util-buffer-from": 3.303.0 - tslib: ^2.5.0 - checksum: 38b5d908c2b0efeca843c371cb8b1461fc1decb1d7603da3fe4f7f338b3495d2283ec7c186742af5e1d104ff79744b447f7eac181028684db98efe94e40a3af2 + "@babel/highlight": ^7.22.5 + checksum: cfe804f518f53faaf9a1d3e0f9f74127ab9a004912c3a16fda07fb6a633393ecb9918a053cb71804204c1b7ec3d49e1699604715e2cfb0c9f7bc4933d324ebb6 languageName: node linkType: hard -"@babel/code-frame@npm:^7.0.0, @babel/code-frame@npm:^7.16.7, @babel/code-frame@npm:^7.18.6, @babel/code-frame@npm:^7.21.4": - version: 7.21.4 - resolution: "@babel/code-frame@npm:7.21.4" - dependencies: - "@babel/highlight": ^7.18.6 - checksum: e5390e6ec1ac58dcef01d4f18eaf1fd2f1325528661ff6d4a5de8979588b9f5a8e852a54a91b923846f7a5c681b217f0a45c2524eb9560553160cd963b7d592c - languageName: node - linkType: hard - -"@babel/compat-data@npm:^7.21.4": - version: 7.21.4 - resolution: "@babel/compat-data@npm:7.21.4" - checksum: 5f8b98c66f2ffba9f3c3a82c0cf354c52a0ec5ad4797b370dc32bdcd6e136ac4febe5e93d76ce76e175632e2dbf6ce9f46319aa689fcfafa41b6e49834fa4b66 +"@babel/compat-data@npm:^7.22.9": + version: 7.22.9 + resolution: "@babel/compat-data@npm:7.22.9" + checksum: bed77d9044ce948b4327b30dd0de0779fa9f3a7ed1f2d31638714ed00229fa71fc4d1617ae0eb1fad419338d3658d0e9a5a083297451e09e73e078d0347ff808 languageName: node linkType: hard @@ -1305,25 +610,25 @@ __metadata: linkType: hard "@babel/core@npm:^7.7.5": - version: 7.21.4 - resolution: "@babel/core@npm:7.21.4" + version: 7.22.9 + resolution: "@babel/core@npm:7.22.9" dependencies: "@ampproject/remapping": ^2.2.0 - "@babel/code-frame": ^7.21.4 - "@babel/generator": ^7.21.4 - "@babel/helper-compilation-targets": ^7.21.4 - "@babel/helper-module-transforms": ^7.21.2 - "@babel/helpers": ^7.21.0 - "@babel/parser": ^7.21.4 - "@babel/template": ^7.20.7 - "@babel/traverse": ^7.21.4 - "@babel/types": ^7.21.4 + "@babel/code-frame": ^7.22.5 + "@babel/generator": ^7.22.9 + "@babel/helper-compilation-targets": ^7.22.9 + "@babel/helper-module-transforms": ^7.22.9 + "@babel/helpers": ^7.22.6 + "@babel/parser": ^7.22.7 + "@babel/template": ^7.22.5 + "@babel/traverse": ^7.22.8 + "@babel/types": ^7.22.5 convert-source-map: ^1.7.0 debug: ^4.1.0 gensync: ^1.0.0-beta.2 json5: ^2.2.2 - semver: ^6.3.0 - checksum: a3beebb2cc79908a02f27a07dc381bcb34e8ecc58fa99f568ad0934c49e12111fc977ee9c5b51eb7ea2da66f63155d37c4dd96b6472eaeecfc35843ccb56bf3d + semver: ^6.3.1 + checksum: 7bf069aeceb417902c4efdaefab1f7b94adb7dea694a9aed1bda2edf4135348a080820529b1a300c6f8605740a00ca00c19b2d5e74b5dd489d99d8c11d5e56d1 languageName: node linkType: hard @@ -1338,142 +643,141 @@ __metadata: languageName: node linkType: hard -"@babel/generator@npm:^7.17.3, @babel/generator@npm:^7.17.7, @babel/generator@npm:^7.21.4": - version: 7.21.4 - resolution: "@babel/generator@npm:7.21.4" +"@babel/generator@npm:^7.17.3, @babel/generator@npm:^7.17.7, @babel/generator@npm:^7.22.7, @babel/generator@npm:^7.22.9": + version: 7.22.9 + resolution: "@babel/generator@npm:7.22.9" dependencies: - "@babel/types": ^7.21.4 + "@babel/types": ^7.22.5 "@jridgewell/gen-mapping": ^0.3.2 "@jridgewell/trace-mapping": ^0.3.17 jsesc: ^2.5.1 - checksum: 9ffbb526a53bb8469b5402f7b5feac93809b09b2a9f82fcbfcdc5916268a65dae746a1f2479e03ba4fb0776facd7c892191f63baa61ab69b2cfdb24f7b92424d + checksum: 7c9d2c58b8d5ac5e047421a6ab03ec2ff5d9a5ff2c2212130a0055e063ac349e0b19d435537d6886c999771aef394832e4f54cd9fc810100a7f23d982f6af06b languageName: node linkType: hard -"@babel/helper-compilation-targets@npm:^7.17.7, @babel/helper-compilation-targets@npm:^7.21.4": - version: 7.21.4 - resolution: "@babel/helper-compilation-targets@npm:7.21.4" +"@babel/helper-compilation-targets@npm:^7.17.7, @babel/helper-compilation-targets@npm:^7.22.9": + version: 7.22.9 + resolution: "@babel/helper-compilation-targets@npm:7.22.9" dependencies: - "@babel/compat-data": ^7.21.4 - "@babel/helper-validator-option": ^7.21.0 - browserslist: ^4.21.3 + "@babel/compat-data": ^7.22.9 + "@babel/helper-validator-option": ^7.22.5 + browserslist: ^4.21.9 lru-cache: ^5.1.1 - semver: ^6.3.0 + semver: ^6.3.1 peerDependencies: "@babel/core": ^7.0.0 - checksum: bf9c7d3e7e6adff9222c05d898724cd4ee91d7eb9d52222c7ad2a22955620c2872cc2d9bdf0e047df8efdb79f4e3af2a06b53f509286145feccc4d10ddc318be + checksum: ea0006c6a93759025f4a35a25228ae260538c9f15023e8aac2a6d45ca68aef4cf86cfc429b19af9a402cbdd54d5de74ad3fbcf6baa7e48184dc079f1a791e178 languageName: node linkType: hard -"@babel/helper-environment-visitor@npm:^7.16.7, @babel/helper-environment-visitor@npm:^7.18.9": - version: 7.18.9 - resolution: "@babel/helper-environment-visitor@npm:7.18.9" - checksum: b25101f6162ddca2d12da73942c08ad203d7668e06663df685634a8fde54a98bc015f6f62938e8554457a592a024108d45b8f3e651fd6dcdb877275b73cc4420 +"@babel/helper-environment-visitor@npm:^7.16.7, @babel/helper-environment-visitor@npm:^7.22.5": + version: 7.22.5 + resolution: "@babel/helper-environment-visitor@npm:7.22.5" + checksum: 248532077d732a34cd0844eb7b078ff917c3a8ec81a7f133593f71a860a582f05b60f818dc5049c2212e5baa12289c27889a4b81d56ef409b4863db49646c4b1 languageName: node linkType: hard -"@babel/helper-function-name@npm:^7.16.7, @babel/helper-function-name@npm:^7.21.0": - version: 7.21.0 - resolution: "@babel/helper-function-name@npm:7.21.0" +"@babel/helper-function-name@npm:^7.16.7, @babel/helper-function-name@npm:^7.22.5": + version: 7.22.5 + resolution: "@babel/helper-function-name@npm:7.22.5" dependencies: - "@babel/template": ^7.20.7 - "@babel/types": ^7.21.0 - checksum: d63e63c3e0e3e8b3138fa47b0cd321148a300ef12b8ee951196994dcd2a492cc708aeda94c2c53759a5c9177fffaac0fd8778791286746f72a000976968daf4e + "@babel/template": ^7.22.5 + "@babel/types": ^7.22.5 + checksum: 6b1f6ce1b1f4e513bf2c8385a557ea0dd7fa37971b9002ad19268ca4384bbe90c09681fe4c076013f33deabc63a53b341ed91e792de741b4b35e01c00238177a languageName: node linkType: hard -"@babel/helper-hoist-variables@npm:^7.16.7, @babel/helper-hoist-variables@npm:^7.18.6": - version: 7.18.6 - resolution: "@babel/helper-hoist-variables@npm:7.18.6" +"@babel/helper-hoist-variables@npm:^7.16.7, @babel/helper-hoist-variables@npm:^7.22.5": + version: 7.22.5 + resolution: "@babel/helper-hoist-variables@npm:7.22.5" dependencies: - "@babel/types": ^7.18.6 - checksum: fd9c35bb435fda802bf9ff7b6f2df06308a21277c6dec2120a35b09f9de68f68a33972e2c15505c1a1a04b36ec64c9ace97d4a9e26d6097b76b4396b7c5fa20f + "@babel/types": ^7.22.5 + checksum: 394ca191b4ac908a76e7c50ab52102669efe3a1c277033e49467913c7ed6f7c64d7eacbeabf3bed39ea1f41731e22993f763b1edce0f74ff8563fd1f380d92cc languageName: node linkType: hard -"@babel/helper-module-imports@npm:^7.18.6": - version: 7.21.4 - resolution: "@babel/helper-module-imports@npm:7.21.4" +"@babel/helper-module-imports@npm:^7.22.5": + version: 7.22.5 + resolution: "@babel/helper-module-imports@npm:7.22.5" dependencies: - "@babel/types": ^7.21.4 - checksum: bd330a2edaafeb281fbcd9357652f8d2666502567c0aad71db926e8499c773c9ea9c10dfaae30122452940326d90c8caff5c649ed8e1bf15b23f858758d3abc6 + "@babel/types": ^7.22.5 + checksum: 9ac2b0404fa38b80bdf2653fbeaf8e8a43ccb41bd505f9741d820ed95d3c4e037c62a1bcdcb6c9527d7798d2e595924c4d025daed73283badc180ada2c9c49ad languageName: node linkType: hard -"@babel/helper-module-transforms@npm:^7.17.7, @babel/helper-module-transforms@npm:^7.21.2": - version: 7.21.2 - resolution: "@babel/helper-module-transforms@npm:7.21.2" +"@babel/helper-module-transforms@npm:^7.17.7, @babel/helper-module-transforms@npm:^7.22.9": + version: 7.22.9 + resolution: "@babel/helper-module-transforms@npm:7.22.9" dependencies: - "@babel/helper-environment-visitor": ^7.18.9 - "@babel/helper-module-imports": ^7.18.6 - "@babel/helper-simple-access": ^7.20.2 - "@babel/helper-split-export-declaration": ^7.18.6 - "@babel/helper-validator-identifier": ^7.19.1 - "@babel/template": ^7.20.7 - "@babel/traverse": ^7.21.2 - "@babel/types": ^7.21.2 - checksum: 8a1c129a4f90bdf97d8b6e7861732c9580f48f877aaaafbc376ce2482febebcb8daaa1de8bc91676d12886487603f8c62a44f9e90ee76d6cac7f9225b26a49e1 + "@babel/helper-environment-visitor": ^7.22.5 + "@babel/helper-module-imports": ^7.22.5 + "@babel/helper-simple-access": ^7.22.5 + "@babel/helper-split-export-declaration": ^7.22.6 + "@babel/helper-validator-identifier": ^7.22.5 + peerDependencies: + "@babel/core": ^7.0.0 + checksum: 2751f77660518cf4ff027514d6f4794f04598c6393be7b04b8e46c6e21606e11c19f3f57ab6129a9c21bacdf8b3ffe3af87bb401d972f34af2d0ffde02ac3001 languageName: node linkType: hard -"@babel/helper-simple-access@npm:^7.20.2": - version: 7.20.2 - resolution: "@babel/helper-simple-access@npm:7.20.2" +"@babel/helper-simple-access@npm:^7.22.5": + version: 7.22.5 + resolution: "@babel/helper-simple-access@npm:7.22.5" dependencies: - "@babel/types": ^7.20.2 - checksum: ad1e96ee2e5f654ffee2369a586e5e8d2722bf2d8b028a121b4c33ebae47253f64d420157b9f0a8927aea3a9e0f18c0103e74fdd531815cf3650a0a4adca11a1 + "@babel/types": ^7.22.5 + checksum: fe9686714caf7d70aedb46c3cce090f8b915b206e09225f1e4dbc416786c2fdbbee40b38b23c268b7ccef749dd2db35f255338fb4f2444429874d900dede5ad2 languageName: node linkType: hard -"@babel/helper-split-export-declaration@npm:^7.16.7, @babel/helper-split-export-declaration@npm:^7.18.6": - version: 7.18.6 - resolution: "@babel/helper-split-export-declaration@npm:7.18.6" +"@babel/helper-split-export-declaration@npm:^7.16.7, @babel/helper-split-export-declaration@npm:^7.22.6": + version: 7.22.6 + resolution: "@babel/helper-split-export-declaration@npm:7.22.6" dependencies: - "@babel/types": ^7.18.6 - checksum: c6d3dede53878f6be1d869e03e9ffbbb36f4897c7cc1527dc96c56d127d834ffe4520a6f7e467f5b6f3c2843ea0e81a7819d66ae02f707f6ac057f3d57943a2b + "@babel/types": ^7.22.5 + checksum: e141cace583b19d9195f9c2b8e17a3ae913b7ee9b8120246d0f9ca349ca6f03cb2c001fd5ec57488c544347c0bb584afec66c936511e447fd20a360e591ac921 languageName: node linkType: hard -"@babel/helper-string-parser@npm:^7.19.4": - version: 7.19.4 - resolution: "@babel/helper-string-parser@npm:7.19.4" - checksum: b2f8a3920b30dfac81ec282ac4ad9598ea170648f8254b10f475abe6d944808fb006aab325d3eb5a8ad3bea8dfa888cfa6ef471050dae5748497c110ec060943 +"@babel/helper-string-parser@npm:^7.22.5": + version: 7.22.5 + resolution: "@babel/helper-string-parser@npm:7.22.5" + checksum: 836851ca5ec813077bbb303acc992d75a360267aa3b5de7134d220411c852a6f17de7c0d0b8c8dcc0f567f67874c00f4528672b2a4f1bc978a3ada64c8c78467 languageName: node linkType: hard -"@babel/helper-validator-identifier@npm:^7.16.7, @babel/helper-validator-identifier@npm:^7.18.6, @babel/helper-validator-identifier@npm:^7.19.1": - version: 7.19.1 - resolution: "@babel/helper-validator-identifier@npm:7.19.1" - checksum: 0eca5e86a729162af569b46c6c41a63e18b43dbe09fda1d2a3c8924f7d617116af39cac5e4cd5d431bb760b4dca3c0970e0c444789b1db42bcf1fa41fbad0a3a +"@babel/helper-validator-identifier@npm:^7.16.7, @babel/helper-validator-identifier@npm:^7.22.5": + version: 7.22.5 + resolution: "@babel/helper-validator-identifier@npm:7.22.5" + checksum: 7f0f30113474a28298c12161763b49de5018732290ca4de13cdaefd4fd0d635a6fe3f6686c37a02905fb1e64f21a5ee2b55140cf7b070e729f1bd66866506aea languageName: node linkType: hard -"@babel/helper-validator-option@npm:^7.21.0": - version: 7.21.0 - resolution: "@babel/helper-validator-option@npm:7.21.0" - checksum: 8ece4c78ffa5461fd8ab6b6e57cc51afad59df08192ed5d84b475af4a7193fc1cb794b59e3e7be64f3cdc4df7ac78bf3dbb20c129d7757ae078e6279ff8c2f07 +"@babel/helper-validator-option@npm:^7.22.5": + version: 7.22.5 + resolution: "@babel/helper-validator-option@npm:7.22.5" + checksum: bbeca8a85ee86990215c0424997438b388b8d642d69b9f86c375a174d3cdeb270efafd1ff128bc7a1d370923d13b6e45829ba8581c027620e83e3a80c5c414b3 languageName: node linkType: hard -"@babel/helpers@npm:^7.17.8, @babel/helpers@npm:^7.21.0": - version: 7.21.0 - resolution: "@babel/helpers@npm:7.21.0" +"@babel/helpers@npm:^7.17.8, @babel/helpers@npm:^7.22.6": + version: 7.22.6 + resolution: "@babel/helpers@npm:7.22.6" dependencies: - "@babel/template": ^7.20.7 - "@babel/traverse": ^7.21.0 - "@babel/types": ^7.21.0 - checksum: 9370dad2bb665c551869a08ac87c8bdafad53dbcdce1f5c5d498f51811456a3c005d9857562715151a0f00b2e912ac8d89f56574f837b5689f5f5072221cdf54 + "@babel/template": ^7.22.5 + "@babel/traverse": ^7.22.6 + "@babel/types": ^7.22.5 + checksum: 5c1f33241fe7bf7709868c2105134a0a86dca26a0fbd508af10a89312b1f77ca38ebae43e50be3b208613c5eacca1559618af4ca236f0abc55d294800faeff30 languageName: node linkType: hard -"@babel/highlight@npm:^7.18.6": - version: 7.18.6 - resolution: "@babel/highlight@npm:7.18.6" +"@babel/highlight@npm:^7.22.5": + version: 7.22.5 + resolution: "@babel/highlight@npm:7.22.5" dependencies: - "@babel/helper-validator-identifier": ^7.18.6 + "@babel/helper-validator-identifier": ^7.22.5 chalk: ^2.0.0 js-tokens: ^4.0.0 - checksum: 92d8ee61549de5ff5120e945e774728e5ccd57fd3b2ed6eace020ec744823d4a98e242be1453d21764a30a14769ecd62170fba28539b211799bbaf232bbb2789 + checksum: f61ae6de6ee0ea8d9b5bcf2a532faec5ab0a1dc0f7c640e5047fc61630a0edb88b18d8c92eb06566d30da7a27db841aca11820ecd3ebe9ce514c9350fbed39c4 languageName: node linkType: hard @@ -1486,32 +790,32 @@ __metadata: languageName: node linkType: hard -"@babel/parser@npm:^7.16.4, @babel/parser@npm:^7.17.3, @babel/parser@npm:^7.17.8, @babel/parser@npm:^7.20.7, @babel/parser@npm:^7.21.4": - version: 7.21.4 - resolution: "@babel/parser@npm:7.21.4" +"@babel/parser@npm:^7.17.3, @babel/parser@npm:^7.17.8, @babel/parser@npm:^7.20.15, @babel/parser@npm:^7.21.3, @babel/parser@npm:^7.22.5, @babel/parser@npm:^7.22.7": + version: 7.22.7 + resolution: "@babel/parser@npm:7.22.7" bin: parser: ./bin/babel-parser.js - checksum: de610ecd1bff331766d0c058023ca11a4f242bfafefc42caf926becccfb6756637d167c001987ca830dd4b34b93c629a4cef63f8c8c864a8564cdfde1989ac77 + checksum: 02209ddbd445831ee8bf966fdf7c29d189ed4b14343a68eb2479d940e7e3846340d7cc6bd654a5f3d87d19dc84f49f50a58cf9363bee249dc5409ff3ba3dab54 languageName: node linkType: hard "@babel/runtime@npm:^7.15.4": - version: 7.21.0 - resolution: "@babel/runtime@npm:7.21.0" + version: 7.22.6 + resolution: "@babel/runtime@npm:7.22.6" dependencies: regenerator-runtime: ^0.13.11 - checksum: 7b33e25bfa9e0e1b9e8828bb61b2d32bdd46b41b07ba7cb43319ad08efc6fda8eb89445193e67d6541814627df0ca59122c0ea795e412b99c5183a0540d338ab + checksum: e585338287c4514a713babf4fdb8fc2a67adcebab3e7723a739fc62c79cfda875b314c90fd25f827afb150d781af97bc16c85bfdbfa2889f06053879a1ddb597 languageName: node linkType: hard -"@babel/template@npm:^7.16.7, @babel/template@npm:^7.20.7": - version: 7.20.7 - resolution: "@babel/template@npm:7.20.7" +"@babel/template@npm:^7.16.7, @babel/template@npm:^7.22.5": + version: 7.22.5 + resolution: "@babel/template@npm:7.22.5" dependencies: - "@babel/code-frame": ^7.18.6 - "@babel/parser": ^7.20.7 - "@babel/types": ^7.20.7 - checksum: 2eb1a0ab8d415078776bceb3473d07ab746e6bb4c2f6ca46ee70efb284d75c4a32bb0cd6f4f4946dec9711f9c0780e8e5d64b743208deac6f8e9858afadc349e + "@babel/code-frame": ^7.22.5 + "@babel/parser": ^7.22.5 + "@babel/types": ^7.22.5 + checksum: c5746410164039aca61829cdb42e9a55410f43cace6f51ca443313f3d0bdfa9a5a330d0b0df73dc17ef885c72104234ae05efede37c1cc8a72dc9f93425977a3 languageName: node linkType: hard @@ -1533,21 +837,21 @@ __metadata: languageName: node linkType: hard -"@babel/traverse@npm:^7.17.3, @babel/traverse@npm:^7.21.0, @babel/traverse@npm:^7.21.2, @babel/traverse@npm:^7.21.4": - version: 7.21.4 - resolution: "@babel/traverse@npm:7.21.4" +"@babel/traverse@npm:^7.17.3, @babel/traverse@npm:^7.22.6, @babel/traverse@npm:^7.22.8": + version: 7.22.8 + resolution: "@babel/traverse@npm:7.22.8" dependencies: - "@babel/code-frame": ^7.21.4 - "@babel/generator": ^7.21.4 - "@babel/helper-environment-visitor": ^7.18.9 - "@babel/helper-function-name": ^7.21.0 - "@babel/helper-hoist-variables": ^7.18.6 - "@babel/helper-split-export-declaration": ^7.18.6 - "@babel/parser": ^7.21.4 - "@babel/types": ^7.21.4 + "@babel/code-frame": ^7.22.5 + "@babel/generator": ^7.22.7 + "@babel/helper-environment-visitor": ^7.22.5 + "@babel/helper-function-name": ^7.22.5 + "@babel/helper-hoist-variables": ^7.22.5 + "@babel/helper-split-export-declaration": ^7.22.6 + "@babel/parser": ^7.22.7 + "@babel/types": ^7.22.5 debug: ^4.1.0 globals: ^11.1.0 - checksum: f22f067c2d9b6497abf3d4e53ea71f3aa82a21f2ed434dd69b8c5767f11f2a4c24c8d2f517d2312c9e5248e5c69395fdca1c95a2b3286122c75f5783ddb6f53c + checksum: a381369bc3eedfd13ed5fef7b884657f1c29024ea7388198149f0edc34bd69ce3966e9f40188d15f56490a5e12ba250ccc485f2882b53d41b054fccefb233e33 languageName: node linkType: hard @@ -1561,14 +865,14 @@ __metadata: languageName: node linkType: hard -"@babel/types@npm:^7.17.0, @babel/types@npm:^7.18.6, @babel/types@npm:^7.20.2, @babel/types@npm:^7.20.7, @babel/types@npm:^7.21.0, @babel/types@npm:^7.21.2, @babel/types@npm:^7.21.4, @babel/types@npm:^7.8.3": - version: 7.21.4 - resolution: "@babel/types@npm:7.21.4" +"@babel/types@npm:^7.17.0, @babel/types@npm:^7.22.5, @babel/types@npm:^7.8.3": + version: 7.22.5 + resolution: "@babel/types@npm:7.22.5" dependencies: - "@babel/helper-string-parser": ^7.19.4 - "@babel/helper-validator-identifier": ^7.19.1 + "@babel/helper-string-parser": ^7.22.5 + "@babel/helper-validator-identifier": ^7.22.5 to-fast-properties: ^2.0.0 - checksum: 587bc55a91ce003b0f8aa10d70070f8006560d7dc0360dc0406d306a2cb2a10154e2f9080b9c37abec76907a90b330a536406cb75e6bdc905484f37b75c73219 + checksum: c13a9c1dc7d2d1a241a2f8363540cb9af1d66e978e8984b400a20c4f38ba38ca29f06e26a0f2d49a70bad9e57615dac09c35accfddf1bb90d23cd3e0a0bab892 languageName: node linkType: hard @@ -1610,52 +914,45 @@ __metadata: languageName: node linkType: hard -"@eslint-community/regexpp@npm:^4.4.0": - version: 4.5.0 - resolution: "@eslint-community/regexpp@npm:4.5.0" - checksum: 99c01335947dbd7f2129e954413067e217ccaa4e219fe0917b7d2bd96135789384b8fedbfb8eb09584d5130b27a7b876a7150ab7376f51b3a0c377d5ce026a10 +"@eslint-community/regexpp@npm:^4.4.0, @eslint-community/regexpp@npm:^4.6.1": + version: 4.6.2 + resolution: "@eslint-community/regexpp@npm:4.6.2" + checksum: a3c341377b46b54fa228f455771b901d1a2717f95d47dcdf40199df30abc000ba020f747f114f08560d119e979d882a94cf46cfc51744544d54b00319c0f2724 languageName: node linkType: hard -"@eslint/eslintrc@npm:^2.0.2": - version: 2.0.2 - resolution: "@eslint/eslintrc@npm:2.0.2" +"@eslint/eslintrc@npm:^2.1.1": + version: 2.1.1 + resolution: "@eslint/eslintrc@npm:2.1.1" dependencies: ajv: ^6.12.4 debug: ^4.3.2 - espree: ^9.5.1 + espree: ^9.6.0 globals: ^13.19.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 - checksum: cfcf5e12c7b2c4476482e7f12434e76eae16fcd163ee627309adb10b761e5caa4a4e52ed7be464423320ff3d11eca5b50de5bf8be3e25834222470835dd5c801 - languageName: node - linkType: hard - -"@eslint/js@npm:8.37.0": - version: 8.37.0 - resolution: "@eslint/js@npm:8.37.0" - checksum: 7a07fb085c94ce1538949012c292fd3a6cd734f149bc03af6157dfbd8a7477678899ef57b4a27e15b36470a997389ad79a0533d5880c71e67720ae1a7de7c62d + checksum: bf909ea183d27238c257a82d4ffdec38ca94b906b4b8dfae02ecbe7ecc9e5a8182ef5e469c808bb8cb4fea4750f43ac4ca7c4b4a167b6cd7e3aaacd386b2bd25 languageName: node linkType: hard -"@gar/promisify@npm:^1.1.3": - version: 1.1.3 - resolution: "@gar/promisify@npm:1.1.3" - checksum: 4059f790e2d07bf3c3ff3e0fec0daa8144fe35c1f6e0111c9921bd32106adaa97a4ab096ad7dab1e28ee6a9060083c4d1a4ada42a7f5f3f7a96b8812e2b757c1 +"@eslint/js@npm:^8.46.0": + version: 8.46.0 + resolution: "@eslint/js@npm:8.46.0" + checksum: 7aed479832302882faf5bec37e9d068f270f84c19b3fb529646a7c1b031e73a312f730569c78806492bc09cfce3d7651dfab4ce09a56cbb06bc6469449e56377 languageName: node linkType: hard -"@humanwhocodes/config-array@npm:^0.11.8": - version: 0.11.8 - resolution: "@humanwhocodes/config-array@npm:0.11.8" +"@humanwhocodes/config-array@npm:^0.11.10": + version: 0.11.10 + resolution: "@humanwhocodes/config-array@npm:0.11.10" dependencies: "@humanwhocodes/object-schema": ^1.2.1 debug: ^4.1.1 minimatch: ^3.0.5 - checksum: 0fd6b3c54f1674ce0a224df09b9c2f9846d20b9e54fabae1281ecfc04f2e6ad69bf19e1d6af6a28f88e8aa3990168b6cb9e1ef755868c3256a630605ec2cb1d3 + checksum: 1b1302e2403d0e35bc43e66d67a2b36b0ad1119efc704b5faff68c41f791a052355b010fb2d27ef022670f550de24cd6d08d5ecf0821c16326b7dcd0ee5d5d8a languageName: node linkType: hard @@ -1673,6 +970,20 @@ __metadata: languageName: node linkType: hard +"@isaacs/cliui@npm:^8.0.2": + version: 8.0.2 + resolution: "@isaacs/cliui@npm:8.0.2" + 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" + checksum: 4a473b9b32a7d4d3cfb7a614226e555091ff0c5a29a1734c28c72a182c2f6699b26fc6b5c2131dfd841e86b185aea714c72201d7c98c2fba5f17709333a67aeb + languageName: node + linkType: hard + "@istanbuljs/load-nyc-config@npm:^1.0.0": version: 1.1.0 resolution: "@istanbuljs/load-nyc-config@npm:1.1.0" @@ -1704,48 +1015,52 @@ __metadata: languageName: node linkType: hard -"@jridgewell/gen-mapping@npm:^0.1.0": - version: 0.1.1 - resolution: "@jridgewell/gen-mapping@npm:0.1.1" - dependencies: - "@jridgewell/set-array": ^1.0.0 - "@jridgewell/sourcemap-codec": ^1.4.10 - checksum: 3bcc21fe786de6ffbf35c399a174faab05eb23ce6a03e8769569de28abbf4facc2db36a9ddb0150545ae23a8d35a7cf7237b2aa9e9356a7c626fb4698287d5cc - languageName: node - linkType: hard - -"@jridgewell/gen-mapping@npm:^0.3.2": - version: 0.3.2 - resolution: "@jridgewell/gen-mapping@npm:0.3.2" +"@jridgewell/gen-mapping@npm:^0.3.0, @jridgewell/gen-mapping@npm:^0.3.2": + version: 0.3.3 + resolution: "@jridgewell/gen-mapping@npm:0.3.3" dependencies: "@jridgewell/set-array": ^1.0.1 "@jridgewell/sourcemap-codec": ^1.4.10 "@jridgewell/trace-mapping": ^0.3.9 - checksum: 1832707a1c476afebe4d0fbbd4b9434fdb51a4c3e009ab1e9938648e21b7a97049fa6009393bdf05cab7504108413441df26d8a3c12193996e65493a4efb6882 + checksum: 4a74944bd31f22354fc01c3da32e83c19e519e3bbadafa114f6da4522ea77dd0c2842607e923a591d60a76699d819a2fbb6f3552e277efdb9b58b081390b60ab languageName: node linkType: hard -"@jridgewell/resolve-uri@npm:3.1.0, @jridgewell/resolve-uri@npm:^3.0.3": +"@jridgewell/resolve-uri@npm:3.1.0": version: 3.1.0 resolution: "@jridgewell/resolve-uri@npm:3.1.0" checksum: b5ceaaf9a110fcb2780d1d8f8d4a0bfd216702f31c988d8042e5f8fbe353c55d9b0f55a1733afdc64806f8e79c485d2464680ac48a0d9fcadb9548ee6b81d267 languageName: node linkType: hard -"@jridgewell/set-array@npm:^1.0.0, @jridgewell/set-array@npm:^1.0.1": +"@jridgewell/resolve-uri@npm:^3.0.3": + version: 3.1.1 + resolution: "@jridgewell/resolve-uri@npm:3.1.1" + checksum: f5b441fe7900eab4f9155b3b93f9800a916257f4e8563afbcd3b5a5337b55e52bd8ae6735453b1b745457d9f6cdb16d74cd6220bbdd98cf153239e13f6cbb653 + languageName: node + linkType: hard + +"@jridgewell/set-array@npm:^1.0.1": version: 1.1.2 resolution: "@jridgewell/set-array@npm:1.1.2" checksum: 69a84d5980385f396ff60a175f7177af0b8da4ddb81824cb7016a9ef914eee9806c72b6b65942003c63f7983d4f39a5c6c27185bbca88eb4690b62075602e28e languageName: node linkType: hard -"@jridgewell/sourcemap-codec@npm:1.4.14, @jridgewell/sourcemap-codec@npm:^1.4.10": +"@jridgewell/sourcemap-codec@npm:1.4.14": version: 1.4.14 resolution: "@jridgewell/sourcemap-codec@npm:1.4.14" checksum: 61100637b6d173d3ba786a5dff019e1a74b1f394f323c1fee337ff390239f053b87266c7a948777f4b1ee68c01a8ad0ab61e5ff4abb5a012a0b091bec391ab97 languageName: node linkType: hard +"@jridgewell/sourcemap-codec@npm:^1.4.10, @jridgewell/sourcemap-codec@npm:^1.4.15": + version: 1.4.15 + resolution: "@jridgewell/sourcemap-codec@npm:1.4.15" + checksum: b881c7e503db3fc7f3c1f35a1dd2655a188cc51a3612d76efc8a6eb74728bef5606e6758ee77423e564092b4a518aba569bbb21c9bac5ab7a35b0c6ae7e344c8 + languageName: node + linkType: hard + "@jridgewell/trace-mapping@npm:0.3.9": version: 0.3.9 resolution: "@jridgewell/trace-mapping@npm:0.3.9" @@ -1757,12 +1072,12 @@ __metadata: linkType: hard "@jridgewell/trace-mapping@npm:^0.3.17, @jridgewell/trace-mapping@npm:^0.3.9": - version: 0.3.17 - resolution: "@jridgewell/trace-mapping@npm:0.3.17" + version: 0.3.18 + resolution: "@jridgewell/trace-mapping@npm:0.3.18" dependencies: "@jridgewell/resolve-uri": 3.1.0 "@jridgewell/sourcemap-codec": 1.4.14 - checksum: 9d703b859cff5cd83b7308fd457a431387db5db96bd781a63bf48e183418dd9d3d44e76b9e4ae13237f6abeeb25d739ec9215c1d5bfdd08f66f750a50074a339 + checksum: 0572669f855260808c16fe8f78f5f1b4356463b11d3f2c7c0b5580c8ba1cbf4ae53efe9f627595830856e57dbac2325ac17eb0c3dd0ec42102e6f227cc289c02 languageName: node linkType: hard @@ -1802,23 +1117,19 @@ __metadata: languageName: node linkType: hard -"@npmcli/fs@npm:^2.1.0": - version: 2.1.2 - resolution: "@npmcli/fs@npm:2.1.2" +"@npmcli/fs@npm:^3.1.0": + version: 3.1.0 + resolution: "@npmcli/fs@npm:3.1.0" dependencies: - "@gar/promisify": ^1.1.3 semver: ^7.3.5 - checksum: 405074965e72d4c9d728931b64d2d38e6ea12066d4fad651ac253d175e413c06fe4350970c783db0d749181da8fe49c42d3880bd1cbc12cd68e3a7964d820225 + checksum: a50a6818de5fc557d0b0e6f50ec780a7a02ab8ad07e5ac8b16bf519e0ad60a144ac64f97d05c443c3367235d337182e1d012bbac0eb8dbae8dc7b40b193efd0e languageName: node linkType: hard -"@npmcli/move-file@npm:^2.0.0": - version: 2.0.1 - resolution: "@npmcli/move-file@npm:2.0.1" - dependencies: - mkdirp: ^1.0.4 - rimraf: ^3.0.2 - checksum: 52dc02259d98da517fae4cb3a0a3850227bdae4939dda1980b788a7670636ca2b4a01b58df03dd5f65c1e3cb70c50fa8ce5762b582b3f499ec30ee5ce1fd9380 +"@pkgjs/parseargs@npm:^0.11.0": + version: 0.11.0 + resolution: "@pkgjs/parseargs@npm:0.11.0" + checksum: 6ad6a00fc4f2f2cfc6bff76fb1d88b8ee20bc0601e18ebb01b6d4be583733a860239a521a7fbca73b612e66705078809483549d2b18f370eb346c5155c8e4a0f languageName: node linkType: hard @@ -1840,12 +1151,21 @@ __metadata: languageName: node linkType: hard +"@sinonjs/commons@npm:^3.0.0": + version: 3.0.0 + resolution: "@sinonjs/commons@npm:3.0.0" + dependencies: + type-detect: 4.0.8 + checksum: b4b5b73d4df4560fb8c0c7b38c7ad4aeabedd362f3373859d804c988c725889cde33550e4bcc7cd316a30f5152a2d1d43db71b6d0c38f5feef71fd8d016763f8 + languageName: node + linkType: hard + "@sinonjs/fake-timers@npm:^10.0.2": - version: 10.0.2 - resolution: "@sinonjs/fake-timers@npm:10.0.2" + version: 10.3.0 + resolution: "@sinonjs/fake-timers@npm:10.3.0" dependencies: - "@sinonjs/commons": ^2.0.0 - checksum: c62aa98e7cefda8dedc101ce227abc888dc46b8ff9706c5f0a8dfd9c3ada97d0a5611384738d9ba0b26b59f99c2ba24efece8e779bb08329e9e87358fa309824 + "@sinonjs/commons": ^3.0.0 + checksum: 614d30cb4d5201550c940945d44c9e0b6d64a888ff2cd5b357f95ad6721070d6b8839cd10e15b76bf5e14af0bcc1d8f9ec00d49a46318f1f669a4bec1d7f3148 languageName: node linkType: hard @@ -1876,439 +1196,430 @@ __metadata: languageName: node linkType: hard -"@smithy/abort-controller@npm:^1.0.2": - version: 1.0.2 - resolution: "@smithy/abort-controller@npm:1.0.2" +"@smithy/abort-controller@npm:^2.0.1": + version: 2.0.1 + resolution: "@smithy/abort-controller@npm:2.0.1" dependencies: - "@smithy/types": ^1.1.1 + "@smithy/types": ^2.0.2 tslib: ^2.5.0 - checksum: 5d0c5b04636c1e27c812b3ac0f780602ff28ed60dbd5b9d39e9caa6f2e982b340efd3fea4d7e7b282451d1469ef6f9d5086e79a0945a078db2db7672dd174f49 + checksum: ec126164886a6f44ed83bde7599cd1a16e20cf429c05bf68897d06220b638aab6c7b6b3b937e65fc25d383d015ed2218ca8c1afaed2ff65fad197d0df2ccebc7 languageName: node linkType: hard -"@smithy/config-resolver@npm:^1.0.1, @smithy/config-resolver@npm:^1.0.2": - version: 1.0.2 - resolution: "@smithy/config-resolver@npm:1.0.2" +"@smithy/config-resolver@npm:^2.0.1": + version: 2.0.1 + resolution: "@smithy/config-resolver@npm:2.0.1" dependencies: - "@smithy/types": ^1.1.1 - "@smithy/util-config-provider": ^1.0.2 - "@smithy/util-middleware": ^1.0.2 + "@smithy/types": ^2.0.2 + "@smithy/util-config-provider": ^2.0.0 + "@smithy/util-middleware": ^2.0.0 tslib: ^2.5.0 - checksum: 23efcb59d682b7ab05ec920c92d9e7fcecc58b9ab204a4de504da18ef0687c051b27acf9bcfc91969a160ea137d6f2f2091b4f3e31c659327784f95240ec0d13 + checksum: ded6c77fd29ab026b5dcc1b4c4adb417515f7fd18af80f487f1868273ae98003d35ff7c6286207895fc74f5098e6eb8a94555a5af89818796a2fb8bb01a80c60 languageName: node linkType: hard -"@smithy/credential-provider-imds@npm:^1.0.1, @smithy/credential-provider-imds@npm:^1.0.2": - version: 1.0.2 - resolution: "@smithy/credential-provider-imds@npm:1.0.2" +"@smithy/credential-provider-imds@npm:^2.0.0, @smithy/credential-provider-imds@npm:^2.0.1": + version: 2.0.1 + resolution: "@smithy/credential-provider-imds@npm:2.0.1" dependencies: - "@smithy/node-config-provider": ^1.0.2 - "@smithy/property-provider": ^1.0.2 - "@smithy/types": ^1.1.1 - "@smithy/url-parser": ^1.0.2 + "@smithy/node-config-provider": ^2.0.1 + "@smithy/property-provider": ^2.0.1 + "@smithy/types": ^2.0.2 + "@smithy/url-parser": ^2.0.1 tslib: ^2.5.0 - checksum: 91b57891b68fd1e83b40d8bde72cbc38247982ba6eaaf32a80bdcccb1e30b42799ac7d8b50da55796ae873a95891b567298d5da41e1059150e240d075a82ee03 + checksum: cf0ee4b50da5584685afc9019af1a8e8c910890dc3128a574b606b7845d898cd65429263682ef27da79dc358286f48b4452617a2fe9fcb98e5483908ac05ce8b languageName: node linkType: hard -"@smithy/eventstream-codec@npm:^1.0.2": - version: 1.0.2 - resolution: "@smithy/eventstream-codec@npm:1.0.2" +"@smithy/eventstream-codec@npm:^2.0.1": + version: 2.0.1 + resolution: "@smithy/eventstream-codec@npm:2.0.1" dependencies: "@aws-crypto/crc32": 3.0.0 - "@smithy/types": ^1.1.1 - "@smithy/util-hex-encoding": ^1.0.2 + "@smithy/types": ^2.0.2 + "@smithy/util-hex-encoding": ^2.0.0 tslib: ^2.5.0 - checksum: 64e2f6e5b03595815a2b968938459db3df31498b2befa657b5acc55242c57da56825431465bac271344cecf68d1f31a54ed3477ebd7d775ad3791d7e48a2bc62 + checksum: 2b7dc1f974b5302dd9fc0982c2484a4d6286e512db78c1c0b8796a5916c3846644b02c6dbbc8433181aeb41c5468d73635459e9f37d640a0bdaf3fe670da8914 languageName: node linkType: hard -"@smithy/fetch-http-handler@npm:^1.0.1, @smithy/fetch-http-handler@npm:^1.0.2": - version: 1.0.2 - resolution: "@smithy/fetch-http-handler@npm:1.0.2" +"@smithy/fetch-http-handler@npm:^2.0.1": + version: 2.0.1 + resolution: "@smithy/fetch-http-handler@npm:2.0.1" dependencies: - "@smithy/protocol-http": ^1.1.1 - "@smithy/querystring-builder": ^1.0.2 - "@smithy/types": ^1.1.1 - "@smithy/util-base64": ^1.0.2 + "@smithy/protocol-http": ^2.0.1 + "@smithy/querystring-builder": ^2.0.1 + "@smithy/types": ^2.0.2 + "@smithy/util-base64": ^2.0.0 tslib: ^2.5.0 - checksum: 62965f69ebcb7a7a67576d4ac909873080d0da33796f15ecf7b265e7f35e0d43e6d3cdf5edb53b9a51f97a88ee90777dd8a7a84ac8dd914cd3d4bcb1c6611506 + checksum: ba80a2182d3c68a88212a99cbeb1bd645f5f6f7584081763faf5590443266e9f32b15e8f7380eaf92ed61fe9fdfbdec1ec030eb0c02b68ed48b4fa710c6718e3 languageName: node linkType: hard -"@smithy/hash-node@npm:^1.0.1": - version: 1.0.2 - resolution: "@smithy/hash-node@npm:1.0.2" +"@smithy/hash-node@npm:^2.0.1": + version: 2.0.1 + resolution: "@smithy/hash-node@npm:2.0.1" dependencies: - "@smithy/types": ^1.1.1 - "@smithy/util-buffer-from": ^1.0.2 - "@smithy/util-utf8": ^1.0.2 + "@smithy/types": ^2.0.2 + "@smithy/util-buffer-from": ^2.0.0 + "@smithy/util-utf8": ^2.0.0 tslib: ^2.5.0 - checksum: 018ef3271c60e7c8df07c4b5f77b52010a8a6be53e9da3f7e361cda48a6df6f5a52870a7721110338725e83b88d5ef120c2312334b6618f3dfdf80d4e0725cab + checksum: e1a1b1ea44f42eacd982680877c31ce4ea4243455bc16e89357981c60902f93074874148701bc82776f41e66eacc6b16c8cc46cd7c99f4135ba13f4ba4a44edf languageName: node linkType: hard -"@smithy/invalid-dependency@npm:^1.0.1": - version: 1.0.2 - resolution: "@smithy/invalid-dependency@npm:1.0.2" +"@smithy/invalid-dependency@npm:^2.0.1": + version: 2.0.1 + resolution: "@smithy/invalid-dependency@npm:2.0.1" dependencies: - "@smithy/types": ^1.1.1 + "@smithy/types": ^2.0.2 tslib: ^2.5.0 - checksum: 51d4bfc25854789a312d87c9bfe1a919e8d7a96052d4010f411c0cb532673a8a3fff92f2d0cf18ccbd05f618f36bfc7fab61159878559ffdd7096de2bd749916 + checksum: d50f5781c9e0b1cb336fa20979ee69640bfa586139237eacae9f9068e40c8c729b3b4646af5fab0250e8e49649acab14262d3524f515ecbecc58cd24b68b7ef5 languageName: node linkType: hard -"@smithy/is-array-buffer@npm:^1.0.2": - version: 1.0.2 - resolution: "@smithy/is-array-buffer@npm:1.0.2" +"@smithy/is-array-buffer@npm:^2.0.0": + version: 2.0.0 + resolution: "@smithy/is-array-buffer@npm:2.0.0" dependencies: tslib: ^2.5.0 - checksum: 811b100b809bc1730ed33231ae22afbce5ccc8d2d5db5c8e86b52db718d738283337a43f777397d1308c24243d0be3e3d862684e9be1dbdfb547a8d8399f0911 + checksum: 6d101cf509a7818667f42d297894f88f86ef41d3cc9d02eae38bbe5e69b16edf83b8e67eb691964d859a16a4e39db1aad323d83f6ae55ae4512a14ff6406c02d languageName: node linkType: hard -"@smithy/middleware-content-length@npm:^1.0.1": - version: 1.0.2 - resolution: "@smithy/middleware-content-length@npm:1.0.2" +"@smithy/middleware-content-length@npm:^2.0.1": + version: 2.0.1 + resolution: "@smithy/middleware-content-length@npm:2.0.1" dependencies: - "@smithy/protocol-http": ^1.1.1 - "@smithy/types": ^1.1.1 + "@smithy/protocol-http": ^2.0.1 + "@smithy/types": ^2.0.2 tslib: ^2.5.0 - checksum: 4b00aa741179152cbdc2aa1229077d0a39196f7ce916a11d121cfa160af380651e031f6a8e59193061bbca0baf5fff5f6010b1a998a6c0b03f228fa862625a8a + checksum: f9074ba2d5780b0d55e46aeacff8276fb11acf828afdc813ec3c5f118323533f774dd2b99ae00c946993c5ba91c66afa5b40d66ce5f035a456cf6c4cd38a83e1 languageName: node linkType: hard -"@smithy/middleware-endpoint@npm:^1.0.1": - version: 1.0.3 - resolution: "@smithy/middleware-endpoint@npm:1.0.3" +"@smithy/middleware-endpoint@npm:^2.0.1": + version: 2.0.1 + resolution: "@smithy/middleware-endpoint@npm:2.0.1" dependencies: - "@smithy/middleware-serde": ^1.0.2 - "@smithy/types": ^1.1.1 - "@smithy/url-parser": ^1.0.2 - "@smithy/util-middleware": ^1.0.2 + "@smithy/middleware-serde": ^2.0.1 + "@smithy/types": ^2.0.2 + "@smithy/url-parser": ^2.0.1 + "@smithy/util-middleware": ^2.0.0 tslib: ^2.5.0 - checksum: 8081b515955632828cdf6955368e46b5288f996fd6697dacf1d44eced488033f8f9d7a10e036fcebabc7f4e72dfcd69ab8ac76b4c17082b9b8326431441ef1a7 + checksum: 5aa0a4c6972a533936dba5eb0d719bb9b534484a401f1a6135a201fc683083aa925d207f2155e15656b2c941c05de2a2aa74dae5e0b9d1a183371a3c3b90b3f6 languageName: node linkType: hard -"@smithy/middleware-retry@npm:^1.0.1, @smithy/middleware-retry@npm:^1.0.2": - version: 1.0.4 - resolution: "@smithy/middleware-retry@npm:1.0.4" +"@smithy/middleware-retry@npm:^2.0.1": + version: 2.0.1 + resolution: "@smithy/middleware-retry@npm:2.0.1" dependencies: - "@smithy/protocol-http": ^1.1.1 - "@smithy/service-error-classification": ^1.0.3 - "@smithy/types": ^1.1.1 - "@smithy/util-middleware": ^1.0.2 - "@smithy/util-retry": ^1.0.4 + "@smithy/protocol-http": ^2.0.1 + "@smithy/service-error-classification": ^2.0.0 + "@smithy/types": ^2.0.2 + "@smithy/util-middleware": ^2.0.0 + "@smithy/util-retry": ^2.0.0 tslib: ^2.5.0 uuid: ^8.3.2 - checksum: 3ca5c4abe3cbfba1201a7777c0f41ae9df0985a1f155bab647cd88d86c8101f4de0bc118164a14d4ac8bd41d120d0c0f1b327b3512c341aeb06f60b0a32dc89d + checksum: a1dd6d1feb560632075f3b3be2f3d406635dc3a158e286eb43f3b93c52deacb2a7c7262f3c911cb652f42e4cb4091155482b04a6c0c61360b650468a54f944b2 languageName: node linkType: hard -"@smithy/middleware-serde@npm:^1.0.1, @smithy/middleware-serde@npm:^1.0.2": - version: 1.0.2 - resolution: "@smithy/middleware-serde@npm:1.0.2" +"@smithy/middleware-serde@npm:^2.0.1": + version: 2.0.1 + resolution: "@smithy/middleware-serde@npm:2.0.1" dependencies: - "@smithy/types": ^1.1.1 + "@smithy/types": ^2.0.2 tslib: ^2.5.0 - checksum: 563045c0ad6fd37548197a3844716e61bf969f4a7b3fbb6c6f0129d4745a0c373029acf069d142a59f1d22778e90f86869cd4ef3f9b297b04a23ef042c6c9c55 + checksum: 115d2b94925f24e3592d6e3889179c30c8992957021bfdc67496f9e36dcb3ffe2282a01c0e439282e89c0a5b56bf125a1bec2c87aed02e325316e3b6050ed9fa languageName: node linkType: hard -"@smithy/middleware-stack@npm:^1.0.1, @smithy/middleware-stack@npm:^1.0.2": - version: 1.0.2 - resolution: "@smithy/middleware-stack@npm:1.0.2" +"@smithy/middleware-stack@npm:^2.0.0": + version: 2.0.0 + resolution: "@smithy/middleware-stack@npm:2.0.0" dependencies: tslib: ^2.5.0 - checksum: 34794d1e6d8b5fd9be70d5d0137f09d8ec88942d054575e35dbf6407f2040c3d0d667d26ff01132c4c42933785127d12b6406a1802af65a779e44cbf38fc9fe2 + checksum: dd23dff4da44964e936c5ae465de9416bb8dd67da2ae72ffe450156ad52e82475836ed5c18d82cef7edeca421b33d363889549e34482008eeb9ca0bb97f061f2 languageName: node linkType: hard -"@smithy/node-config-provider@npm:^1.0.1, @smithy/node-config-provider@npm:^1.0.2": - version: 1.0.2 - resolution: "@smithy/node-config-provider@npm:1.0.2" +"@smithy/node-config-provider@npm:^2.0.1": + version: 2.0.1 + resolution: "@smithy/node-config-provider@npm:2.0.1" dependencies: - "@smithy/property-provider": ^1.0.2 - "@smithy/shared-ini-file-loader": ^1.0.2 - "@smithy/types": ^1.1.1 + "@smithy/property-provider": ^2.0.1 + "@smithy/shared-ini-file-loader": ^2.0.1 + "@smithy/types": ^2.0.2 tslib: ^2.5.0 - checksum: 593bd5adf0177851a191849e68760f7ebde943db2613898ecda468b31c47442e5fd380fdd95fe95f912bd15fe48a28dbde3f352b49a8108fc34c630d7eb50843 + checksum: 9752c8e7c01fc991b93bb080e8486b82d55d592a2c7573004c2e296c192c153b967c79c03be0924c59e14ffc3de04ca861e99370d2ae9a0d8c54f25ea3f99be8 languageName: node linkType: hard -"@smithy/node-http-handler@npm:^1.0.1, @smithy/node-http-handler@npm:^1.0.2, @smithy/node-http-handler@npm:^1.0.3": - version: 1.0.3 - resolution: "@smithy/node-http-handler@npm:1.0.3" +"@smithy/node-http-handler@npm:^2.0.1": + version: 2.0.1 + resolution: "@smithy/node-http-handler@npm:2.0.1" dependencies: - "@smithy/abort-controller": ^1.0.2 - "@smithy/protocol-http": ^1.1.1 - "@smithy/querystring-builder": ^1.0.2 - "@smithy/types": ^1.1.1 + "@smithy/abort-controller": ^2.0.1 + "@smithy/protocol-http": ^2.0.1 + "@smithy/querystring-builder": ^2.0.1 + "@smithy/types": ^2.0.2 tslib: ^2.5.0 - checksum: 727e1391ccc409b3ed7d7a697bfcd29e4e9fbc81404665719c47c8143832ef8f65ea9f8f5d783ea1930fd56a0d45eaaa6a7a8db76da785ba73f0fd29276fbe11 + checksum: 04705e7c28fb71a5800a511f64dd182230a501742f779860b1d2155a02eec9c9df3b573e2ad9cd3a0148891f02b780edb7803c95b321dd4a2659715ac73566ee languageName: node linkType: hard -"@smithy/property-provider@npm:^1.0.1, @smithy/property-provider@npm:^1.0.2": - version: 1.0.2 - resolution: "@smithy/property-provider@npm:1.0.2" +"@smithy/property-provider@npm:^2.0.0, @smithy/property-provider@npm:^2.0.1": + version: 2.0.1 + resolution: "@smithy/property-provider@npm:2.0.1" dependencies: - "@smithy/types": ^1.1.1 + "@smithy/types": ^2.0.2 tslib: ^2.5.0 - checksum: 55d09de3dab2e5858b45f981c67ec1cf4e8fc28a758b9830fd0c5e04ca6e9d69f31e92c80fa4fba5d8eed4a60b4f95528d41f9a6c63e2ff0aad726d3c3b83636 + checksum: 56ea840032911b37a2e602144cf4cf08401a79a3ab06e226a4cf65a14b77e70e4d0bd8f762d172e322a0cf75f45bd1886a265687e090a2848798225e276f2882 languageName: node linkType: hard -"@smithy/protocol-http@npm:^1.0.1, @smithy/protocol-http@npm:^1.1.0, @smithy/protocol-http@npm:^1.1.1": - version: 1.1.1 - resolution: "@smithy/protocol-http@npm:1.1.1" +"@smithy/protocol-http@npm:^2.0.1": + version: 2.0.1 + resolution: "@smithy/protocol-http@npm:2.0.1" dependencies: - "@smithy/types": ^1.1.1 + "@smithy/types": ^2.0.2 tslib: ^2.5.0 - checksum: 6320e8b010d05a123efef19401b3e2fc0d03efa99082ad0d5fd41a3209a05332acb4b5ee21dcfd4764b6576019884602bd09ede0f7508540baf7ddb1f7eb49f5 + checksum: cc3d354fad3f27ab29cf7053bbdbbd150dca1864595a46d463abf06595af68e5c31cfc2d03b971fbb693cb9abd3be5763a195497a4fe19f8aed3132069ae3246 languageName: node linkType: hard -"@smithy/querystring-builder@npm:^1.0.2": - version: 1.0.2 - resolution: "@smithy/querystring-builder@npm:1.0.2" +"@smithy/querystring-builder@npm:^2.0.1": + version: 2.0.1 + resolution: "@smithy/querystring-builder@npm:2.0.1" dependencies: - "@smithy/types": ^1.1.1 - "@smithy/util-uri-escape": ^1.0.2 + "@smithy/types": ^2.0.2 + "@smithy/util-uri-escape": ^2.0.0 tslib: ^2.5.0 - checksum: c0e6807a5b6c8438295ff8ebc11d9e2a28fe1434405688055a9dd4f4d354e49e14a4e459419af23f74aab0a281d58b70d29ae6777a42504a89d6f089b110ee52 + checksum: f8ec37623054fcbad8317b57ef41fc906f3f22260b365e7c3b107219dcf456c5c7347165f3bf25d47a4c592548d835778f9c8bc7cc7306b65aa6ce429d94a345 languageName: node linkType: hard -"@smithy/querystring-parser@npm:^1.0.2": - version: 1.0.2 - resolution: "@smithy/querystring-parser@npm:1.0.2" +"@smithy/querystring-parser@npm:^2.0.1": + version: 2.0.1 + resolution: "@smithy/querystring-parser@npm:2.0.1" dependencies: - "@smithy/types": ^1.1.1 + "@smithy/types": ^2.0.2 tslib: ^2.5.0 - checksum: 348672b1bc4193de23055421756c47619de5a2dea496dcf85a7744e9a8d5b26a4573dd731f9be936f7b1ba564676ecdf6d166638f9961bff497a2c554c97f01c + checksum: a6107281ae33beb5518b85de82fea7692cfcb6c15155719c6ba6ee44cc75b20f07ce28214c635ee38b0474995c0a23b4872feef8bd9f98a7811b7ccf59bac819 languageName: node linkType: hard -"@smithy/service-error-classification@npm:^1.0.3": - version: 1.0.3 - resolution: "@smithy/service-error-classification@npm:1.0.3" - checksum: adf840de7865606946a68f55ae4f35e03d96422be4df886309300944243510baf1bd84ef0786bcd8184094e97fafdff66a52b8ba669267391a8d8856093edde0 +"@smithy/service-error-classification@npm:^2.0.0": + version: 2.0.0 + resolution: "@smithy/service-error-classification@npm:2.0.0" + checksum: f6f9b869dca8e74871c428e1277dd3700f67b0ca61de69fc838ac55187bbc602193a7d1073113ea6916892babf3f8fe4938b182fc902ce0a415928799a7e3f9a languageName: node linkType: hard -"@smithy/shared-ini-file-loader@npm:^1.0.1, @smithy/shared-ini-file-loader@npm:^1.0.2": - version: 1.0.2 - resolution: "@smithy/shared-ini-file-loader@npm:1.0.2" +"@smithy/shared-ini-file-loader@npm:^2.0.0, @smithy/shared-ini-file-loader@npm:^2.0.1": + version: 2.0.1 + resolution: "@smithy/shared-ini-file-loader@npm:2.0.1" dependencies: - "@smithy/types": ^1.1.1 + "@smithy/types": ^2.0.2 tslib: ^2.5.0 - checksum: c9ba908a48c12a06bcbb872e2e0581805507efc9beffcdbad39baa09b22da63bbcbf538a95d099666869e080ad5556bfe2fab6d4e977a71163595f3f9c1d7edc + checksum: 0f67d0ba9e44286a444301e603260a2ae5973324d43bc89f6ca15d2a830b32c1232474ff452cf3d607ee08d4fa6d17517fd901a4e6fd4dddbc571aa6b1ae3b6d languageName: node linkType: hard -"@smithy/signature-v4@npm:^1.0.1": - version: 1.0.2 - resolution: "@smithy/signature-v4@npm:1.0.2" - dependencies: - "@smithy/eventstream-codec": ^1.0.2 - "@smithy/is-array-buffer": ^1.0.2 - "@smithy/types": ^1.1.1 - "@smithy/util-hex-encoding": ^1.0.2 - "@smithy/util-middleware": ^1.0.2 - "@smithy/util-uri-escape": ^1.0.2 - "@smithy/util-utf8": ^1.0.2 - tslib: ^2.5.0 - checksum: 3e3072ef956adbb9fe633f2e7c28fc9aaeddb7551c25a1af94214fd5e93617c2b0ede440c1cb61ee6268c502ce0418cf124fb21cb654605292a956ec3061d54a - languageName: node - linkType: hard - -"@smithy/smithy-client@npm:^1.0.2, @smithy/smithy-client@npm:^1.0.3": - version: 1.0.4 - resolution: "@smithy/smithy-client@npm:1.0.4" - dependencies: - "@smithy/middleware-stack": ^1.0.2 - "@smithy/types": ^1.1.1 - "@smithy/util-stream": ^1.0.2 +"@smithy/signature-v4@npm:^2.0.0": + version: 2.0.1 + resolution: "@smithy/signature-v4@npm:2.0.1" + dependencies: + "@smithy/eventstream-codec": ^2.0.1 + "@smithy/is-array-buffer": ^2.0.0 + "@smithy/types": ^2.0.2 + "@smithy/util-hex-encoding": ^2.0.0 + "@smithy/util-middleware": ^2.0.0 + "@smithy/util-uri-escape": ^2.0.0 + "@smithy/util-utf8": ^2.0.0 tslib: ^2.5.0 - checksum: 468aa2f28b6addb82861a543a2c1639dcfd40e49d3344237bd56c2d061af5e2bc88972297a2b86de7bf31d7ee49589a4e5a577518ef747e2c7458ae20f7c3322 + checksum: 2c4bc499251dfbdbea0359fc5196002ac805df7f6f87d7cba36164d8fcd16a749a3c1a52259569134628511dc496724d5e76744c1c39fee3a3f42c5fbfe3749b languageName: node linkType: hard -"@smithy/types@npm:1.1.0": - version: 1.1.0 - resolution: "@smithy/types@npm:1.1.0" +"@smithy/smithy-client@npm:^2.0.1": + version: 2.0.1 + resolution: "@smithy/smithy-client@npm:2.0.1" dependencies: + "@smithy/middleware-stack": ^2.0.0 + "@smithy/types": ^2.0.2 + "@smithy/util-stream": ^2.0.1 tslib: ^2.5.0 - checksum: 8c0589fa973e5c71cf776c28c43aba04ee07139578fd0174aac0d74c3688e3ffa7075cecd65b223b2a155ad711808b1e4ad58a084ba9f24fcb49679272018387 + checksum: 5c4abe0a0a67f2c2aa47cbf2806d0d44a34f97ebc83f1add9654d95cef3303fc0dff41237a7e360ae6c2be721e945c889bf869f11476a759c0ad82d61df18f5a languageName: node linkType: hard -"@smithy/types@npm:^1.1.0, @smithy/types@npm:^1.1.1": - version: 1.1.1 - resolution: "@smithy/types@npm:1.1.1" +"@smithy/types@npm:^2.0.2": + version: 2.0.2 + resolution: "@smithy/types@npm:2.0.2" dependencies: tslib: ^2.5.0 - checksum: bf4b632eb7d668d8b99e99facf514d506868121e24c0adfaaa52f7da4056644fda5cb4324355f1dba16fc43a4c9af9ef7853db2a4895c3fca11ac98cf6d12234 + checksum: 4afdd7c77b212abd9e0770a1489057aa0470f8a59061c4fb2175b1f12e02180db3d85e16f2cd870a95c17bd28a5a4b8ef1dff1ade6852f85eafea12872d9588e languageName: node linkType: hard -"@smithy/url-parser@npm:^1.0.1, @smithy/url-parser@npm:^1.0.2": - version: 1.0.2 - resolution: "@smithy/url-parser@npm:1.0.2" +"@smithy/url-parser@npm:^2.0.1": + version: 2.0.1 + resolution: "@smithy/url-parser@npm:2.0.1" dependencies: - "@smithy/querystring-parser": ^1.0.2 - "@smithy/types": ^1.1.1 + "@smithy/querystring-parser": ^2.0.1 + "@smithy/types": ^2.0.2 tslib: ^2.5.0 - checksum: 1844b23a30afb689bc9ec21238d9e6cc428e152201f8f30f252f6211ee8b74760d6568e42125b75bc4d110b15476eac22c204c7e36f6970d04e8afa5bc83ec1d + checksum: 653bdeff812b972fa88a4e2d795c38df1aca68055818d150727b8b7d2b7b6bb00aed003b113febe371ed2e38e8dd4715b31af6afce7e883d937aed75e7ff48fb languageName: node linkType: hard -"@smithy/util-base64@npm:^1.0.1, @smithy/util-base64@npm:^1.0.2": - version: 1.0.2 - resolution: "@smithy/util-base64@npm:1.0.2" +"@smithy/util-base64@npm:^2.0.0": + version: 2.0.0 + resolution: "@smithy/util-base64@npm:2.0.0" dependencies: - "@smithy/util-buffer-from": ^1.0.2 + "@smithy/util-buffer-from": ^2.0.0 tslib: ^2.5.0 - checksum: 618d2952fbbc2cabcc6234c9aad612417a2a6f2fcac556a81b75f59d651e5daea7b2c1b44894b30611a425f32045a2dbe0623ebf57654e2068486d0d5a4b6ac7 + checksum: 52124a684dfac853288acd2a0ffff02559c21bf7faaa3db58a914e4acb4b1f7925fd48593e7545db87f8f962250824d1249dc8be645ecbd2c1dd1728cfe1069b languageName: node linkType: hard -"@smithy/util-body-length-browser@npm:^1.0.1": - version: 1.0.2 - resolution: "@smithy/util-body-length-browser@npm:1.0.2" +"@smithy/util-body-length-browser@npm:^2.0.0": + version: 2.0.0 + resolution: "@smithy/util-body-length-browser@npm:2.0.0" dependencies: tslib: ^2.5.0 - checksum: 3b0eacc3761625aaf5c384da8487a38663325385826ca9ac0fee17b7dff879a2cb3564a03d146e0b66ba6c503e1a07f159a9cccd4d7711b7063516acbfadb496 + checksum: 4bccdd857bd24c9dcb6e9f2d5be03d59415f9a94d660ec7b3efb45e9aa04017f34c387368f176f24233a071af3b7a2b5f8236a2f5a83bfc884d24dfcc341e836 languageName: node linkType: hard -"@smithy/util-body-length-node@npm:^1.0.1": - version: 1.0.2 - resolution: "@smithy/util-body-length-node@npm:1.0.2" +"@smithy/util-body-length-node@npm:^2.0.0": + version: 2.0.0 + resolution: "@smithy/util-body-length-node@npm:2.0.0" dependencies: tslib: ^2.5.0 - checksum: 472371dc0a44a72333111758b8e60428cdca088cfe8a87c98413c81c4083e8988ae9d27f20fa2b0a8aed7fc26c0fec8e13c271494772c619f65f1a9f4e665eb3 + checksum: 47ded0cc99f880eda353700e10d6131289683164d4ef98a24a2d79449d33f3bbf70c1ce0a66fc457e0c8d14be9fb2a3430fa09302bbc3daa5d23129e11fff478 languageName: node linkType: hard -"@smithy/util-buffer-from@npm:^1.0.2": - version: 1.0.2 - resolution: "@smithy/util-buffer-from@npm:1.0.2" +"@smithy/util-buffer-from@npm:^2.0.0": + version: 2.0.0 + resolution: "@smithy/util-buffer-from@npm:2.0.0" dependencies: - "@smithy/is-array-buffer": ^1.0.2 + "@smithy/is-array-buffer": ^2.0.0 tslib: ^2.5.0 - checksum: ca4308ebc54f372777fc53b2782c752ebfdfe3934b1c4da935c78bb2f4f921d2071056edc644704ba894d363f85cc52fc07095d93eb6cbba510ba19a767be265 + checksum: d33cbf3e488d23390c88705ddae71b08de7a87b6453e38b508cd37a22a02e8b5be9f0cd46c1347b496c3977a815a7399b18840544ecdc4cce8cf3dcd0f5bb009 languageName: node linkType: hard -"@smithy/util-config-provider@npm:^1.0.2": - version: 1.0.2 - resolution: "@smithy/util-config-provider@npm:1.0.2" +"@smithy/util-config-provider@npm:^2.0.0": + version: 2.0.0 + resolution: "@smithy/util-config-provider@npm:2.0.0" dependencies: tslib: ^2.5.0 - checksum: 625c100b8af62cb08c167ebd23f77250e62b012e085ec8db8f06e3539fc516c340e14b480a09012192595121a58276dfc4370529237a58393185f3782ed0244a + checksum: cdc34db5b42658a7c98652ddb2e35b31e0d76f22a051d71724927999a53467fb38fe6dcf228585544bc168cbd54ded3913e14cbc33c947d3c8a45ca518a9b7b0 languageName: node linkType: hard -"@smithy/util-defaults-mode-browser@npm:^1.0.1": - version: 1.0.2 - resolution: "@smithy/util-defaults-mode-browser@npm:1.0.2" +"@smithy/util-defaults-mode-browser@npm:^2.0.1": + version: 2.0.1 + resolution: "@smithy/util-defaults-mode-browser@npm:2.0.1" dependencies: - "@smithy/property-provider": ^1.0.2 - "@smithy/types": ^1.1.1 + "@smithy/property-provider": ^2.0.1 + "@smithy/types": ^2.0.2 bowser: ^2.11.0 tslib: ^2.5.0 - checksum: ca03c9f596f9a26d392c289de7af7c7e7bdb12648b0339074bcbaddd762e58cb0cf127b8ed24e269bff004465702986b90d5151c502b418a615449171432024b + checksum: b436183ca880c1bb607116c780e7e8d1e252aeb51daf5f35740281671a4553431f393847d74ad180e9388bf11aeab1c675f61bb3a34a0d2fa9943111b063b17d languageName: node linkType: hard -"@smithy/util-defaults-mode-node@npm:^1.0.1": - version: 1.0.2 - resolution: "@smithy/util-defaults-mode-node@npm:1.0.2" +"@smithy/util-defaults-mode-node@npm:^2.0.1": + version: 2.0.1 + resolution: "@smithy/util-defaults-mode-node@npm:2.0.1" dependencies: - "@smithy/config-resolver": ^1.0.2 - "@smithy/credential-provider-imds": ^1.0.2 - "@smithy/node-config-provider": ^1.0.2 - "@smithy/property-provider": ^1.0.2 - "@smithy/types": ^1.1.1 + "@smithy/config-resolver": ^2.0.1 + "@smithy/credential-provider-imds": ^2.0.1 + "@smithy/node-config-provider": ^2.0.1 + "@smithy/property-provider": ^2.0.1 + "@smithy/types": ^2.0.2 tslib: ^2.5.0 - checksum: 805977758a5cabe3b8a4ee69f29cf6861356096239a60269eac6b83a4d11eddba030b0a5bcdb64add3d1301f611b21ed7d773e354cac8ff08b9d85ba47960252 + checksum: ffd9601a8b37e1ea7a2c23bcaa70bf297c5c95113e8bab41a2f533d1a2f9f9631e66b5360312e202980dbd03ed3c8d8146dd33c8d4e5d3caf30c78a39b301de2 languageName: node linkType: hard -"@smithy/util-hex-encoding@npm:^1.0.2": - version: 1.0.2 - resolution: "@smithy/util-hex-encoding@npm:1.0.2" +"@smithy/util-hex-encoding@npm:^2.0.0": + version: 2.0.0 + resolution: "@smithy/util-hex-encoding@npm:2.0.0" dependencies: tslib: ^2.5.0 - checksum: c70bfccb41ac702b514a2bc3d6f897a616efa5aba880a6c7a7501ea874bea3c2213868cb2c494c19f6a5190f3f9e4f7d73d9ab87addbd243435e287dc892dc30 + checksum: 884373e089d909e3c9805bdb78f367d1f3612e4e1e6d8f0263cc82a8b9689eddc0bc80b8b58aa711bd5b48d9cb124f9996906c172e951c9dac78984459e831cf languageName: node linkType: hard -"@smithy/util-middleware@npm:^1.0.1, @smithy/util-middleware@npm:^1.0.2": - version: 1.0.2 - resolution: "@smithy/util-middleware@npm:1.0.2" +"@smithy/util-middleware@npm:^2.0.0": + version: 2.0.0 + resolution: "@smithy/util-middleware@npm:2.0.0" dependencies: tslib: ^2.5.0 - checksum: 899aca62efab92ac1b55e801b2bb1a3e5eb1b74f52387abe40f902ec2e5f3afd36e2942f392ff1a562e69dd0de901b9aa20c55e4af2ff0e37e20c14b20940852 + checksum: 10401734a10e0c48ed684f20b7a34c40ed85f2e906e61adb6295963d035f2a93b524e80149a252a259a4bca3626773bf89c5eaa2423fd565358c6b4eb9b6d4e0 languageName: node linkType: hard -"@smithy/util-retry@npm:^1.0.1, @smithy/util-retry@npm:^1.0.2, @smithy/util-retry@npm:^1.0.4": - version: 1.0.4 - resolution: "@smithy/util-retry@npm:1.0.4" +"@smithy/util-retry@npm:^2.0.0": + version: 2.0.0 + resolution: "@smithy/util-retry@npm:2.0.0" dependencies: - "@smithy/service-error-classification": ^1.0.3 + "@smithy/service-error-classification": ^2.0.0 tslib: ^2.5.0 - checksum: b9a7e464d67b141a175f126b2a0fc5c91de387eb483b432db4769cee43bbbb546e4142711f293c3a04c82b6d56b676b7a2f7f7150e2c736a10d8e5df1ed63c04 + checksum: d5bfe5e81f41dffce6ba5aaf784f08247602d00f883c10c0de9e34a7f04f5369d7ac43901dd75eecc3864882b7390ad40885d07b3dcb35a366411b639482e673 languageName: node linkType: hard -"@smithy/util-stream@npm:^1.0.2": - version: 1.0.2 - resolution: "@smithy/util-stream@npm:1.0.2" - dependencies: - "@smithy/fetch-http-handler": ^1.0.2 - "@smithy/node-http-handler": ^1.0.3 - "@smithy/types": ^1.1.1 - "@smithy/util-base64": ^1.0.2 - "@smithy/util-buffer-from": ^1.0.2 - "@smithy/util-hex-encoding": ^1.0.2 - "@smithy/util-utf8": ^1.0.2 +"@smithy/util-stream@npm:^2.0.1": + version: 2.0.1 + resolution: "@smithy/util-stream@npm:2.0.1" + dependencies: + "@smithy/fetch-http-handler": ^2.0.1 + "@smithy/node-http-handler": ^2.0.1 + "@smithy/types": ^2.0.2 + "@smithy/util-base64": ^2.0.0 + "@smithy/util-buffer-from": ^2.0.0 + "@smithy/util-hex-encoding": ^2.0.0 + "@smithy/util-utf8": ^2.0.0 tslib: ^2.5.0 - checksum: ebea6886766da812434d4ca39ee201302096451441d577eb5efeb44ce3437f3828df435676a5e453f8eb535e19224bc3d21c2d3e6632a5eef68d932a37343476 + checksum: 3a190a9e5e3675e69ffdb72bec00216cb8c3e5a565cdcea47bff593c8aac34bc39897c870c4ad82ea369a5be5caa63b08a20516653b6de0e0a19a291315c3773 languageName: node linkType: hard -"@smithy/util-uri-escape@npm:^1.0.2": - version: 1.0.2 - resolution: "@smithy/util-uri-escape@npm:1.0.2" +"@smithy/util-uri-escape@npm:^2.0.0": + version: 2.0.0 + resolution: "@smithy/util-uri-escape@npm:2.0.0" dependencies: tslib: ^2.5.0 - checksum: 6df9fb3aed8fc386c1b7ccb2b52268c2e67f3620a85fa42d35f964ae2c492b6395841a751a0ab4068825c7fd54a84933014571d6fbc12e9e1a711d0be3f2c747 + checksum: d201cee524ece997c406902463b5ea0b72599994f7b3ac1d923d5645497e9ef93126d146016f13dd4afafe33b9a3e92faf4e023cf0af510b270c1b9ce3d78da8 languageName: node linkType: hard -"@smithy/util-utf8@npm:^1.0.1, @smithy/util-utf8@npm:^1.0.2": - version: 1.0.2 - resolution: "@smithy/util-utf8@npm:1.0.2" +"@smithy/util-utf8@npm:^2.0.0": + version: 2.0.0 + resolution: "@smithy/util-utf8@npm:2.0.0" dependencies: - "@smithy/util-buffer-from": ^1.0.2 + "@smithy/util-buffer-from": ^2.0.0 tslib: ^2.5.0 - checksum: 64add10ac1bbeb0c96a1ba8b05886ae86cee589fd4cccb25276849748fed7035c54e64d40107f22a57ca9b4f9324afe4baf0007c67536d520c1b8378e1e6ec55 + checksum: bc8cda84f85b513380a61352635b306ae50d3b92974454db32835b39bbaa38150332b89346098ba9dea2e0002e2963fcbdd622bc9b3eec7b7ea8fa3f8c7ce737 languageName: node linkType: hard -"@smithy/util-waiter@npm:^1.0.1": - version: 1.0.2 - resolution: "@smithy/util-waiter@npm:1.0.2" +"@smithy/util-waiter@npm:^2.0.1": + version: 2.0.1 + resolution: "@smithy/util-waiter@npm:2.0.1" dependencies: - "@smithy/abort-controller": ^1.0.2 - "@smithy/types": ^1.1.1 + "@smithy/abort-controller": ^2.0.1 + "@smithy/types": ^2.0.2 tslib: ^2.5.0 - checksum: 07b5284b2d10c4ddcfd61ce7bc2585f3ba401cf42dffa799317eb1895e9b061c54efd328e204c8e1881888018cd198889bccc86c6a707485fdec3738ed5b4aeb + checksum: d85893533b4222545151d8a0e4c67400a8370bc3dce34c17f5ce0c19b4dadf102ffa7b3ef2b5004abcf0892c318825b07498f8a86f2430cc886f55bf7369a57f languageName: node linkType: hard @@ -2359,9 +1670,9 @@ __metadata: linkType: hard "@tsconfig/node16@npm:^1.0.2": - version: 1.0.3 - resolution: "@tsconfig/node16@npm:1.0.3" - checksum: 3a8b657dd047495b7ad23437d6afd20297ce90380ff0bdee93fc7d39a900dbd8d9e26e53ff6b465e7967ce2adf0b218782590ce9013285121e6a5928fbd6819f + version: 1.0.4 + resolution: "@tsconfig/node16@npm:1.0.4" + checksum: 202319785901f942a6e1e476b872d421baec20cf09f4b266a1854060efbf78cde16a4d256e8bc949d31e6cd9a90f1e8ef8fb06af96a65e98338a2b6b0de0a0ff languageName: node linkType: hard @@ -2394,9 +1705,9 @@ __metadata: linkType: hard "@types/chai@npm:*, @types/chai@npm:^4.3.1": - version: 4.3.4 - resolution: "@types/chai@npm:4.3.4" - checksum: 571184967beb03bf64c4392a13a7d44e72da9af5a1e83077ff81c39cf59c0fda2a5c78d2005084601cf8f3d11726608574d8b5b4a0e3e9736792807afd926cd0 + version: 4.3.5 + resolution: "@types/chai@npm:4.3.5" + checksum: c8f26a88c6b5b53a3275c7f5ff8f107028e3cbb9ff26795fff5f3d9dea07106a54ce9e2dce5e40347f7c4cc35657900aaf0c83934a25a1ae12e61e0f5516e431 languageName: node linkType: hard @@ -2429,13 +1740,14 @@ __metadata: linkType: hard "@types/express-serve-static-core@npm:^4.17.33": - version: 4.17.33 - resolution: "@types/express-serve-static-core@npm:4.17.33" + version: 4.17.35 + resolution: "@types/express-serve-static-core@npm:4.17.35" dependencies: "@types/node": "*" "@types/qs": "*" "@types/range-parser": "*" - checksum: dce580d16b85f207445af9d4053d66942b27d0c72e86153089fa00feee3e96ae336b7bedb31ed4eea9e553c99d6dd356ed6e0928f135375d9f862a1a8015adf2 + "@types/send": "*" + checksum: cc8995d10c6feda475ec1b3a0e69eb0f35f21ab6b49129ad5c6f279e0bc5de8175bc04ec51304cb79a43eec3ed2f5a1e01472eb6d5f827b8c35c6ca8ad24eb6e languageName: node linkType: hard @@ -2466,18 +1778,18 @@ __metadata: linkType: hard "@types/json-schema@npm:^7.0.9": - version: 7.0.11 - resolution: "@types/json-schema@npm:7.0.11" - checksum: 527bddfe62db9012fccd7627794bd4c71beb77601861055d87e3ee464f2217c85fca7a4b56ae677478367bbd248dbde13553312b7d4dbc702a2f2bbf60c4018d + version: 7.0.12 + resolution: "@types/json-schema@npm:7.0.12" + checksum: 00239e97234eeb5ceefb0c1875d98ade6e922bfec39dd365ec6bd360b5c2f825e612ac4f6e5f1d13601b8b30f378f15e6faa805a3a732f4a1bbe61915163d293 languageName: node linkType: hard "@types/jsonwebtoken@npm:^9.0.1": - version: 9.0.1 - resolution: "@types/jsonwebtoken@npm:9.0.1" + version: 9.0.2 + resolution: "@types/jsonwebtoken@npm:9.0.2" dependencies: "@types/node": "*" - checksum: a7f0925e9a42ad3ae970364c63c5986d40da5c83d51d3f4e624eb0f064a380376f9e3fb3f2f837390a9ab80143f5d75fd51866da30e110f6b486a3379e1c768f + checksum: 3bb8d40e78d7eb53e427db6e9f0f22e0890cfee80965dcf741d08341814913afb211306de6e9847c6d241cc8e36f8a59090cbfdcc510ab7c81af9d650c5afe0e languageName: node linkType: hard @@ -2523,8 +1835,8 @@ __metadata: linkType: hard "@types/koa@npm:*, @types/koa@npm:^2.13.4, @types/koa@npm:^2.13.6": - version: 2.13.6 - resolution: "@types/koa@npm:2.13.6" + version: 2.13.8 + resolution: "@types/koa@npm:2.13.8" dependencies: "@types/accepts": "*" "@types/content-disposition": "*" @@ -2534,7 +1846,7 @@ __metadata: "@types/keygrip": "*" "@types/koa-compose": "*" "@types/node": "*" - checksum: a4061c2e29cd4ccb65a704fe3ef6868eac82558856c7c00a5bcc0f9fdf18c595e0156c52508eb8ba926a9108d9cf5c9a1a1b73cccf50e29b37ea1154323d0b26 + checksum: 76a2a6d219c65f242a43efca42970d864701c58319c346a91dd8c3b4df2021786fd0d600a88dfb098358c9085f9f4a2dfe62563641441cf21e11e2bfe04f4fdf languageName: node linkType: hard @@ -2547,6 +1859,15 @@ __metadata: languageName: node linkType: hard +"@types/mandrill-api@npm:^1.0.33": + version: 1.0.33 + resolution: "@types/mandrill-api@npm:1.0.33" + dependencies: + "@types/node": "*" + checksum: e739cb330faa54533fe096ab5a27ba509c956db7ae14e9a336f00d48bd51ea15abde9f477d578263880c5fdb7bd651012db277d8901f542ce77b088c49f8a338 + languageName: node + linkType: hard + "@types/mime@npm:*": version: 3.0.1 resolution: "@types/mime@npm:3.0.1" @@ -2554,6 +1875,13 @@ __metadata: languageName: node linkType: hard +"@types/mime@npm:^1": + version: 1.3.2 + resolution: "@types/mime@npm:1.3.2" + checksum: 0493368244cced1a69cb791b485a260a422e6fcc857782e1178d1e6f219f1b161793e9f87f5fae1b219af0f50bee24fcbe733a18b4be8fdd07a38a8fb91146fd + languageName: node + linkType: hard + "@types/mocha@npm:^9.1.1": version: 9.1.1 resolution: "@types/mocha@npm:9.1.1" @@ -2562,16 +1890,16 @@ __metadata: linkType: hard "@types/node@npm:*, @types/node@npm:>=8.1.0": - version: 18.15.11 - resolution: "@types/node@npm:18.15.11" - checksum: 977b4ad04708897ff0eb049ecf82246d210939c82461922d20f7d2dcfd81bbc661582ba3af28869210f7e8b1934529dcd46bff7d448551400f9d48b9d3bddec3 + version: 20.4.7 + resolution: "@types/node@npm:20.4.7" + checksum: a40d7003f66b56220a2028179e49f950b46fa6dbf860a4a6ecbd6ba7976f05b2f0b31ced39689ec88a7d9e32d07e088c6a06d270b99d5bc13a28291ac2f30ca7 languageName: node linkType: hard "@types/node@npm:^18.16.1": - version: 18.16.1 - resolution: "@types/node@npm:18.16.1" - checksum: 799026b949a48993cba7c9b81b2eabfdfb34c880744cb44c1c990fbedc9e315f3634d126eb2cf9a6e0795577c01016e2326d98565bef695ada9d363fadeb6946 + version: 18.17.2 + resolution: "@types/node@npm:18.17.2" + checksum: 2e08acaeb30e05f96fbf3d974fc2203bf10ed9020cafd511018dbba8023044bb8758f9d5a2a8e68da3bca827ccef7628202de9b7ca3b515374a7b094b128930a languageName: node linkType: hard @@ -2597,19 +1925,30 @@ __metadata: linkType: hard "@types/semver@npm:^7.3.12": - version: 7.3.13 - resolution: "@types/semver@npm:7.3.13" - checksum: 00c0724d54757c2f4bc60b5032fe91cda6410e48689633d5f35ece8a0a66445e3e57fa1d6e07eb780f792e82ac542948ec4d0b76eb3484297b79bd18b8cf1cb0 + version: 7.5.0 + resolution: "@types/semver@npm:7.5.0" + checksum: 0a64b9b9c7424d9a467658b18dd70d1d781c2d6f033096a6e05762d20ebbad23c1b69b0083b0484722aabf35640b78ccc3de26368bcae1129c87e9df028a22e2 + languageName: node + linkType: hard + +"@types/send@npm:*": + version: 0.17.1 + resolution: "@types/send@npm:0.17.1" + dependencies: + "@types/mime": ^1 + "@types/node": "*" + checksum: 10b620a5960058ef009afbc17686f680d6486277c62f640845381ec4baa0ea683fdd77c3afea4803daf5fcddd3fb2972c8aa32e078939f1d4e96f83195c89793 languageName: node linkType: hard "@types/serve-static@npm:*": - version: 1.15.1 - resolution: "@types/serve-static@npm:1.15.1" + version: 1.15.2 + resolution: "@types/serve-static@npm:1.15.2" dependencies: + "@types/http-errors": "*" "@types/mime": "*" "@types/node": "*" - checksum: 2e078bdc1e458c7dfe69e9faa83cc69194b8896cce57cb745016580543c7ab5af07fdaa8ac1765eb79524208c81017546f66056f44d1204f812d72810613de36 + checksum: 15c261dbfc57890f7cc17c04d5b22b418dfa0330c912b46c5d8ae2064da5d6f844ef7f41b63c7f4bbf07675e97ebe6ac804b032635ec742ae45d6f1274259b3e languageName: node linkType: hard @@ -2624,11 +1963,11 @@ __metadata: linkType: hard "@types/sinon@npm:*, @types/sinon@npm:^10.0.11": - version: 10.0.13 - resolution: "@types/sinon@npm:10.0.13" + version: 10.0.16 + resolution: "@types/sinon@npm:10.0.16" dependencies: "@types/sinonjs__fake-timers": "*" - checksum: 46a14c888db50f0098ec53d451877e0111d878ec4a653b9e9ed7f8e54de386d6beb0e528ddc3e95cd3361a8ab9ad54e4cca33cd88d45b9227b83e9fc8fb6688a + checksum: 1216aac584500d6bf845ca76f57e82f8459cf9de4ed80a55e50aa4438360fc418789a42181e211c5d279e97f86a3a994e3c81e43971d540737caca0193242bbf languageName: node linkType: hard @@ -2646,16 +1985,23 @@ __metadata: languageName: node linkType: hard +"@types/validator@npm:^13.11.7": + version: 13.11.7 + resolution: "@types/validator@npm:13.11.7" + checksum: 975ad31728f3e32278f090545b879453d5d2b26dd159c6b632efb79e748711bca15e6339b93e85c33b48208b1aee262d3043246118aa3c67a74fb0f89700b1d5 + languageName: node + linkType: hard + "@typescript-eslint/eslint-plugin@npm:^5.25.0": - version: 5.57.0 - resolution: "@typescript-eslint/eslint-plugin@npm:5.57.0" + version: 5.62.0 + resolution: "@typescript-eslint/eslint-plugin@npm:5.62.0" dependencies: "@eslint-community/regexpp": ^4.4.0 - "@typescript-eslint/scope-manager": 5.57.0 - "@typescript-eslint/type-utils": 5.57.0 - "@typescript-eslint/utils": 5.57.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 - grapheme-splitter: ^1.0.4 + graphemer: ^1.4.0 ignore: ^5.2.0 natural-compare-lite: ^1.4.0 semver: ^7.3.7 @@ -2666,43 +2012,43 @@ __metadata: peerDependenciesMeta: typescript: optional: true - checksum: be13aa74ee6f15f0ae67781c625d9dcf3ce8a3feca2b125eef0cfee850b7f9f0cec23fc56a729ef25926298fe3ea51603ebeee2b93fc9b73fce1410638707177 + checksum: fc104b389c768f9fa7d45a48c86d5c1ad522c1d0512943e782a56b1e3096b2cbcc1eea3fcc590647bf0658eef61aac35120a9c6daf979bf629ad2956deb516a1 languageName: node linkType: hard "@typescript-eslint/parser@npm:^5.25.0": - version: 5.57.0 - resolution: "@typescript-eslint/parser@npm:5.57.0" + version: 5.62.0 + resolution: "@typescript-eslint/parser@npm:5.62.0" dependencies: - "@typescript-eslint/scope-manager": 5.57.0 - "@typescript-eslint/types": 5.57.0 - "@typescript-eslint/typescript-estree": 5.57.0 + "@typescript-eslint/scope-manager": 5.62.0 + "@typescript-eslint/types": 5.62.0 + "@typescript-eslint/typescript-estree": 5.62.0 debug: ^4.3.4 peerDependencies: eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 peerDependenciesMeta: typescript: optional: true - checksum: b7e8345631911f721591ba970fea5c888f0f3bf2e2ea2dbc3e5b0dc345c0776b62b92c534edfde1379b4b182958a421f35ac26d84705fe6ae7dd37aa675d9493 + checksum: d168f4c7f21a7a63f47002e2d319bcbb6173597af5c60c1cf2de046b46c76b4930a093619e69faf2d30214c29ab27b54dcf1efc7046a6a6bd6f37f59a990e752 languageName: node linkType: hard -"@typescript-eslint/scope-manager@npm:5.57.0": - version: 5.57.0 - resolution: "@typescript-eslint/scope-manager@npm:5.57.0" +"@typescript-eslint/scope-manager@npm:5.62.0": + version: 5.62.0 + resolution: "@typescript-eslint/scope-manager@npm:5.62.0" dependencies: - "@typescript-eslint/types": 5.57.0 - "@typescript-eslint/visitor-keys": 5.57.0 - checksum: 4a851f23da2adbf6341b04c1e3f19fcb66415683f26805d3123725d18845bd4a150bd182de0a91279d5682f2568bb5dd831d4ad0bdb70f49d9ca7381cec4dd17 + "@typescript-eslint/types": 5.62.0 + "@typescript-eslint/visitor-keys": 5.62.0 + checksum: 6062d6b797fe1ce4d275bb0d17204c827494af59b5eaf09d8a78cdd39dadddb31074dded4297aaf5d0f839016d601032857698b0e4516c86a41207de606e9573 languageName: node linkType: hard -"@typescript-eslint/type-utils@npm:5.57.0": - version: 5.57.0 - resolution: "@typescript-eslint/type-utils@npm:5.57.0" +"@typescript-eslint/type-utils@npm:5.62.0": + version: 5.62.0 + resolution: "@typescript-eslint/type-utils@npm:5.62.0" dependencies: - "@typescript-eslint/typescript-estree": 5.57.0 - "@typescript-eslint/utils": 5.57.0 + "@typescript-eslint/typescript-estree": 5.62.0 + "@typescript-eslint/utils": 5.62.0 debug: ^4.3.4 tsutils: ^3.21.0 peerDependencies: @@ -2710,23 +2056,23 @@ __metadata: peerDependenciesMeta: typescript: optional: true - checksum: 649d000edabfe4e567b8a384d0012c56396e40ce2123a78857d4b8da6bf2288627dc355745bd7d4a2877d4cc8a26e1d1dbfc422e6382ac3d3ab431b92eb5b852 + checksum: fc41eece5f315dfda14320be0da78d3a971d650ea41300be7196934b9715f3fe1120a80207551eb71d39568275dbbcf359bde540d1ca1439d8be15e9885d2739 languageName: node linkType: hard -"@typescript-eslint/types@npm:5.57.0": - version: 5.57.0 - resolution: "@typescript-eslint/types@npm:5.57.0" - checksum: 79a100fb650965f63c01c20e6abd79ca0d2043c3a329b9fef89917d6b9ba3c0f946dca3f14f2975ee6349daadd6ce0e98fde3aafe4b710e5a27abe1adc590c85 +"@typescript-eslint/types@npm:5.62.0": + version: 5.62.0 + resolution: "@typescript-eslint/types@npm:5.62.0" + checksum: 48c87117383d1864766486f24de34086155532b070f6264e09d0e6139449270f8a9559cfef3c56d16e3bcfb52d83d42105d61b36743626399c7c2b5e0ac3b670 languageName: node linkType: hard -"@typescript-eslint/typescript-estree@npm:5.57.0": - version: 5.57.0 - resolution: "@typescript-eslint/typescript-estree@npm:5.57.0" +"@typescript-eslint/typescript-estree@npm:5.62.0": + version: 5.62.0 + resolution: "@typescript-eslint/typescript-estree@npm:5.62.0" dependencies: - "@typescript-eslint/types": 5.57.0 - "@typescript-eslint/visitor-keys": 5.57.0 + "@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 @@ -2735,105 +2081,105 @@ __metadata: peerDependenciesMeta: typescript: optional: true - checksum: 648b88f88ea6cc293ec67b4c0f4f3c2bf733be7e0f2eee08aadbaec6939fd724a6c287decc336abbf67b9e366cc2c48f2e0e48d8302b533e783f798332a06e83 + checksum: 3624520abb5807ed8f57b1197e61c7b1ed770c56dfcaca66372d584ff50175225798bccb701f7ef129d62c5989070e1ee3a0aa2d84e56d9524dcf011a2bb1a52 languageName: node linkType: hard -"@typescript-eslint/utils@npm:5.57.0": - version: 5.57.0 - resolution: "@typescript-eslint/utils@npm:5.57.0" +"@typescript-eslint/utils@npm:5.62.0": + version: 5.62.0 + resolution: "@typescript-eslint/utils@npm:5.62.0" dependencies: "@eslint-community/eslint-utils": ^4.2.0 "@types/json-schema": ^7.0.9 "@types/semver": ^7.3.12 - "@typescript-eslint/scope-manager": 5.57.0 - "@typescript-eslint/types": 5.57.0 - "@typescript-eslint/typescript-estree": 5.57.0 + "@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 peerDependencies: eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 - checksum: 461258e1194d24c5e642c65ba1afd612712fa8e617ac85cfbbe3dde2557fe4abadedbce19a6954ae0cccbfb92b8a09f38d65a3eedca0394861a5d1c4c893c5ed + checksum: ee9398c8c5db6d1da09463ca7bf36ed134361e20131ea354b2da16a5fdb6df9ba70c62a388d19f6eebb421af1786dbbd79ba95ddd6ab287324fc171c3e28d931 languageName: node linkType: hard -"@typescript-eslint/visitor-keys@npm:5.57.0": - version: 5.57.0 - resolution: "@typescript-eslint/visitor-keys@npm:5.57.0" +"@typescript-eslint/visitor-keys@npm:5.62.0": + version: 5.62.0 + resolution: "@typescript-eslint/visitor-keys@npm:5.62.0" dependencies: - "@typescript-eslint/types": 5.57.0 + "@typescript-eslint/types": 5.62.0 eslint-visitor-keys: ^3.3.0 - checksum: 77d53f74648e48bf1c6313cd60568c2b1539157ac13945f26204a54beb156666c24f3d033dd0db8ed5d1d4595ee63c072732b17132e4488b46763bf8fdcefa49 + checksum: 976b05d103fe8335bef5c93ad3f76d781e3ce50329c0243ee0f00c0fcfb186c81df50e64bfdd34970148113f8ade90887f53e3c4938183afba830b4ba8e30a35 languageName: node linkType: hard -"@vue/compiler-core@npm:3.2.47": - version: 3.2.47 - resolution: "@vue/compiler-core@npm:3.2.47" +"@vue/compiler-core@npm:3.3.4": + version: 3.3.4 + resolution: "@vue/compiler-core@npm:3.3.4" dependencies: - "@babel/parser": ^7.16.4 - "@vue/shared": 3.2.47 + "@babel/parser": ^7.21.3 + "@vue/shared": 3.3.4 estree-walker: ^2.0.2 - source-map: ^0.6.1 - checksum: 9ccc2a0b897b59eea56ca4f92ed29c14cd1184f68532edf5fb0fe5cb2833bcf9e4836029effb6eb9a7c872e9e0350fafdcd96ff00c0b5b79e17ded0c068b5f84 + source-map-js: ^1.0.2 + checksum: 5437942ea6575b316c9cd84f4f128a44939713da8b6958060e152c599e6d771d5db056c398d7574ee706ff8092e0d99ac4f14e7eef8712a8dd923d2323201b9e languageName: node linkType: hard -"@vue/compiler-dom@npm:3.2.47": - version: 3.2.47 - resolution: "@vue/compiler-dom@npm:3.2.47" +"@vue/compiler-dom@npm:3.3.4": + version: 3.3.4 + resolution: "@vue/compiler-dom@npm:3.3.4" dependencies: - "@vue/compiler-core": 3.2.47 - "@vue/shared": 3.2.47 - checksum: 1eced735f865e6df0c2d7fa041f9f27996ff4c0d4baf5fad0f67e65e623215f4394c49bba337b78427c6e71f2cc2db12b19ec6b865b4c057c0a15ccedeb20752 + "@vue/compiler-core": 3.3.4 + "@vue/shared": 3.3.4 + checksum: 1c2ac0c89de8eef7be1c568d57504e6245adaaec40c2c4d9717bc231ca10bf682d918a3b358d24c786eeaf8e0d7eb8a65f57d9044775a304783fde1d069a1896 languageName: node linkType: hard "@vue/compiler-sfc@npm:^3.2.40": - version: 3.2.47 - resolution: "@vue/compiler-sfc@npm:3.2.47" - dependencies: - "@babel/parser": ^7.16.4 - "@vue/compiler-core": 3.2.47 - "@vue/compiler-dom": 3.2.47 - "@vue/compiler-ssr": 3.2.47 - "@vue/reactivity-transform": 3.2.47 - "@vue/shared": 3.2.47 + version: 3.3.4 + resolution: "@vue/compiler-sfc@npm:3.3.4" + dependencies: + "@babel/parser": ^7.20.15 + "@vue/compiler-core": 3.3.4 + "@vue/compiler-dom": 3.3.4 + "@vue/compiler-ssr": 3.3.4 + "@vue/reactivity-transform": 3.3.4 + "@vue/shared": 3.3.4 estree-walker: ^2.0.2 - magic-string: ^0.25.7 + magic-string: ^0.30.0 postcss: ^8.1.10 - source-map: ^0.6.1 - checksum: 4588a513310b9319a00adfdbe789cfe60d5ec19c51e8f2098152b9e81f54be170e16c40463f6b5e4c7ab79796fc31e2de93587a9dd1af136023fa03712b62e68 + source-map-js: ^1.0.2 + checksum: 0a0adfdd3e812f528e25e4b3bbf14b2296b719a8aac609eca42035295527cc253b918a552dc15218e917efef26b7ca94054dc8784a1a18c06c3d4bb4d18ab8b9 languageName: node linkType: hard -"@vue/compiler-ssr@npm:3.2.47": - version: 3.2.47 - resolution: "@vue/compiler-ssr@npm:3.2.47" +"@vue/compiler-ssr@npm:3.3.4": + version: 3.3.4 + resolution: "@vue/compiler-ssr@npm:3.3.4" dependencies: - "@vue/compiler-dom": 3.2.47 - "@vue/shared": 3.2.47 - checksum: 91bc6e46744d5405713c08d8e576971aa6d13a0cde84ec592d3221bf6ee228e49ce12233af8c18dc39723455b420df2951f3616ceb99758eb432485475fa7bc2 + "@vue/compiler-dom": 3.3.4 + "@vue/shared": 3.3.4 + checksum: 5d1875d55ea864080dd90e5d81a29f93308e312faf00163db5b391b38c2fe799fd3eb58955823dc632f2f8bdd271a4534cc0020646b7f82717be1a8d30dc16e7 languageName: node linkType: hard -"@vue/reactivity-transform@npm:3.2.47": - version: 3.2.47 - resolution: "@vue/reactivity-transform@npm:3.2.47" +"@vue/reactivity-transform@npm:3.3.4": + version: 3.3.4 + resolution: "@vue/reactivity-transform@npm:3.3.4" dependencies: - "@babel/parser": ^7.16.4 - "@vue/compiler-core": 3.2.47 - "@vue/shared": 3.2.47 + "@babel/parser": ^7.20.15 + "@vue/compiler-core": 3.3.4 + "@vue/shared": 3.3.4 estree-walker: ^2.0.2 - magic-string: ^0.25.7 - checksum: 6fe54374aa8c080c0c421e18134e84e723e2d3e53178cf084c1cd75bc8b1ffaaf07756801f3aa4e1e7ad1ba76356c28bbab4bc1b676159db8fc10f10f2cbd405 + magic-string: ^0.30.0 + checksum: b425e78b2084ac7037887fbe012dcad5e5963ac9714ae15a04fda1c6766ec8c53ef231de1cfdc4d3cf46bd5d84bfec8ebdccf48da4ff5ee2f4b5084e54f0a1b1 languageName: node linkType: hard -"@vue/shared@npm:3.2.47": - version: 3.2.47 - resolution: "@vue/shared@npm:3.2.47" - checksum: 0aa711dc9160fa0e476e6e94eea4e019398adf2211352d0e4a672cfb6b65b104bbd5d234807d1c091107bdc0f5d818d0f12378987eb7861d39be3aa9f6cd6e3e +"@vue/shared@npm:3.3.4": + version: 3.3.4 + resolution: "@vue/shared@npm:3.3.4" + checksum: 12fe53ff816bfa29ea53f89212067a86512c626b8d30149ff28b36705820f6150e1fb4e4e46897ad9eddb1d1cfc02d8941053939910eed69a905f7a5509baabe languageName: node linkType: hard @@ -2870,12 +2216,12 @@ __metadata: languageName: node linkType: hard -"acorn@npm:^8.4.1, acorn@npm:^8.8.0": - version: 8.8.2 - resolution: "acorn@npm:8.8.2" +"acorn@npm:^8.4.1, acorn@npm:^8.9.0": + version: 8.10.0 + resolution: "acorn@npm:8.10.0" bin: acorn: bin/acorn - checksum: f790b99a1bf63ef160c967e23c46feea7787e531292bb827126334612c234ed489a0dc2c7ba33156416f0ffa8d25bf2b0fdb7f35c2ba60eb3e960572bece4001 + checksum: 538ba38af0cc9e5ef983aee196c4b8b4d87c0c94532334fa7e065b2c8a1f85863467bb774231aae91613fcda5e68740c15d97b1967ae3394d20faddddd8af61d languageName: node linkType: hard @@ -2909,7 +2255,7 @@ __metadata: languageName: node linkType: hard -"ajv@npm:^6.10.0, ajv@npm:^6.12.4": +"ajv@npm:^6.12.4": version: 6.12.6 resolution: "ajv@npm:6.12.6" dependencies: @@ -2969,7 +2315,7 @@ __metadata: languageName: node linkType: hard -"ansi-styles@npm:^6.0.0": +"ansi-styles@npm:^6.0.0, ansi-styles@npm:^6.1.0": version: 6.2.1 resolution: "ansi-styles@npm:6.2.1" checksum: ef940f2f0ced1a6347398da88a91da7930c33ecac3c77b72c5905f8b8fe402c52e6fde304ff5347f616e27a742da3f1dc76de98f6866c69251ad0b07a66776d9 @@ -3059,14 +2405,14 @@ __metadata: linkType: hard "arweave@npm:^1.10.13, arweave@npm:^1.13.4": - version: 1.13.5 - resolution: "arweave@npm:1.13.5" + version: 1.14.4 + resolution: "arweave@npm:1.14.4" dependencies: arconnect: ^0.4.2 asn1.js: ^5.4.1 base64-js: ^1.5.1 bignumber.js: ^9.0.2 - checksum: 9126c61e27c0f888e1f506fcc3df689da1f9827bfe0ced65a51f46afb271cd092e95d221b4d8b7f8d4b056cc8aa6809ae2002ab3a7c679e6439068bf16c3d5a1 + checksum: 99f4705531066508a511ceae1f9492a85758a9b9c19d5d24c79c37af00e025487ab16db6947d8e226ff288941998aa43fbc1954c84e55459e9ba2e3534aa12ce languageName: node linkType: hard @@ -3111,24 +2457,24 @@ __metadata: linkType: hard "axios-mock-adapter@npm:^1.21.2": - version: 1.21.4 - resolution: "axios-mock-adapter@npm:1.21.4" + version: 1.21.5 + resolution: "axios-mock-adapter@npm:1.21.5" dependencies: fast-deep-equal: ^3.1.3 is-buffer: ^2.0.5 peerDependencies: axios: ">= 0.17.0" - checksum: adcd838c8b21f1ed67950c18b68dcf7c7c9bcddc75418720b5a407fd8448a3aa97b736307c631c27c2971368ac56716a310ea6cfc056517c1f1b0f8825427e58 + checksum: e3c2ccf220a2ddd316ccdff36b65754ddaf9b7a8dc70a64e9ff94da335a0694aaba3d925e6d7d5f71dd4badfe9af581bd7e6dd2dc09414d9900298afe12a71a0 languageName: node linkType: hard "axios-retry@npm:^3.4.0": - version: 3.4.0 - resolution: "axios-retry@npm:3.4.0" + version: 3.6.0 + resolution: "axios-retry@npm:3.6.0" dependencies: "@babel/runtime": ^7.15.4 is-retry-allowed: ^2.2.0 - checksum: fae18aeef220cfde22f93ed663d644e1a913a5cfc9760904ffd7a7e5ec8c7fbd53a58e1be461b6b30de19dce178823630655bcb77a487e17000f5e977ef94a2e + checksum: 9ed0879453170a55960dea21116e7f732f1e78acb72eb49d80b1620583814d94bda78200d0767bae82d37cc2ab80752f0aec49717ce4b141858059c0c6b9921c languageName: node linkType: hard @@ -3226,17 +2572,17 @@ __metadata: languageName: node linkType: hard -"browserslist@npm:^4.21.3": - version: 4.21.5 - resolution: "browserslist@npm:4.21.5" +"browserslist@npm:^4.21.9": + version: 4.21.10 + resolution: "browserslist@npm:4.21.10" dependencies: - caniuse-lite: ^1.0.30001449 - electron-to-chromium: ^1.4.284 - node-releases: ^2.0.8 - update-browserslist-db: ^1.0.10 + caniuse-lite: ^1.0.30001517 + electron-to-chromium: ^1.4.477 + node-releases: ^2.0.13 + update-browserslist-db: ^1.0.11 bin: browserslist: cli.js - checksum: 9755986b22e73a6a1497fd8797aedd88e04270be33ce66ed5d85a1c8a798292a65e222b0f251bafa1c2522261e237d73b08b58689d4920a607e5a53d56dc4706 + checksum: 1e27c0f111a35d1dd0e8fc2c61781b0daefabc2c9471b0b10537ce54843014bceb2a1ce4571af1a82b2bf1e6e6e05d38865916689a158f03bc2c7a4ec2577db8 languageName: node linkType: hard @@ -3261,29 +2607,23 @@ __metadata: languageName: node linkType: hard -"cacache@npm:^16.1.0": - version: 16.1.3 - resolution: "cacache@npm:16.1.3" +"cacache@npm:^17.0.0": + version: 17.1.3 + resolution: "cacache@npm:17.1.3" dependencies: - "@npmcli/fs": ^2.1.0 - "@npmcli/move-file": ^2.0.0 - chownr: ^2.0.0 - fs-minipass: ^2.1.0 - glob: ^8.0.1 - infer-owner: ^1.0.4 + "@npmcli/fs": ^3.1.0 + fs-minipass: ^3.0.0 + glob: ^10.2.2 lru-cache: ^7.7.1 - minipass: ^3.1.6 + minipass: ^5.0.0 minipass-collect: ^1.0.2 minipass-flush: ^1.0.5 minipass-pipeline: ^1.2.4 - mkdirp: ^1.0.4 p-map: ^4.0.0 - promise-inflight: ^1.0.1 - rimraf: ^3.0.2 - ssri: ^9.0.0 + ssri: ^10.0.0 tar: ^6.1.11 - unique-filename: ^2.0.0 - checksum: d91409e6e57d7d9a3a25e5dcc589c84e75b178ae8ea7de05cbf6b783f77a5fae938f6e8fda6f5257ed70000be27a681e1e44829251bfffe4c10216002f8f14e6 + unique-filename: ^3.0.0 + checksum: 385756781e1e21af089160d89d7462b7ed9883c978e848c7075b90b73cb823680e66092d61513050164588387d2ca87dd6d910e28d64bc13a9ac82cd8580c796 languageName: node linkType: hard @@ -3340,10 +2680,10 @@ __metadata: languageName: node linkType: hard -"caniuse-lite@npm:^1.0.30001449": - version: 1.0.30001473 - resolution: "caniuse-lite@npm:1.0.30001473" - checksum: 007ad17463612d38080fc59b5fa115ccb1016a1aff8daab92199a7cf8eb91cf987e85e7015cb0bca830ee2ef45f252a016c29a98a6497b334cceb038526b73f1 +"caniuse-lite@npm:^1.0.30001517": + version: 1.0.30001519 + resolution: "caniuse-lite@npm:1.0.30001519" + checksum: 66085133ede05d947e30b62fed2cbae18e5767afda8b0de38840883e1cfe5846bf1568ddbafd31647544e59112355abedaf9c867ac34541bfc20d69e7a19d94c languageName: node linkType: hard @@ -3542,13 +2882,20 @@ __metadata: languageName: node linkType: hard -"colorette@npm:2.0.19, colorette@npm:^2.0.16": +"colorette@npm:2.0.19": version: 2.0.19 resolution: "colorette@npm:2.0.19" checksum: 888cf5493f781e5fcf54ce4d49e9d7d698f96ea2b2ef67906834bb319a392c667f9ec69f4a10e268d2946d13a9503d2d19b3abaaaf174e3451bfe91fb9d82427 languageName: node linkType: hard +"colorette@npm:^2.0.16": + version: 2.0.20 + resolution: "colorette@npm:2.0.20" + checksum: 0c016fea2b91b733eb9f4bcdb580018f52c0bc0979443dad930e5037a968237ac53d9beb98e218d2e9235834f8eebce7f8e080422d6194e957454255bde71d3d + languageName: node + linkType: hard + "colorspace@npm:1.1.x": version: 1.1.4 resolution: "colorspace@npm:1.1.4" @@ -3575,7 +2922,7 @@ __metadata: languageName: node linkType: hard -"commander@npm:^9.1.0, commander@npm:^9.3.0": +"commander@npm:^9.3.0": version: 9.5.0 resolution: "commander@npm:9.5.0" checksum: c7a3e27aa59e913b54a1bafd366b88650bc41d6651f0cbe258d4ff09d43d6a7394232a4dadd0bf518b3e696fdf595db1028a0d82c785b88bd61f8a440cecfade @@ -3796,9 +3143,9 @@ __metadata: linkType: hard "dotenv@npm:^16.0.3": - version: 16.0.3 - resolution: "dotenv@npm:16.0.3" - checksum: afcf03f373d7a6d62c7e9afea6328e62851d627a4e73f2e12d0a8deae1cd375892004f3021883f8aec85932cd2834b091f568ced92b4774625b321db83b827f8 + version: 16.3.1 + resolution: "dotenv@npm:16.3.1" + checksum: 15d75e7279018f4bafd0ee9706593dd14455ddb71b3bcba9c52574460b7ccaf67d5cf8b2c08a5af1a9da6db36c956a04a1192b101ee102a3e0cf8817bbcf3dfd languageName: node linkType: hard @@ -3825,10 +3172,10 @@ __metadata: languageName: node linkType: hard -"electron-to-chromium@npm:^1.4.284": - version: 1.4.348 - resolution: "electron-to-chromium@npm:1.4.348" - checksum: 662491cd395de9eb7f400f139d8abc9a5e6dd33ff2b0eb71c060bdd7570f16ed876e0a57485dec2c42d55ad875f6c9d8f68f72f2ad3d8e1b2ab2cb4c20ee7bc5 +"electron-to-chromium@npm:^1.4.477": + version: 1.4.484 + resolution: "electron-to-chromium@npm:1.4.484" + checksum: 30e1df1f2d32b4be0fab07622d6ac3cc05dfdb70aa8901cfbf0511d7834df82d06478bd3d5fa2c37ca114dc8b3da44fa8f8b8cb994c8ac629f0ecbc351b0c0bb languageName: node linkType: hard @@ -3946,43 +3293,43 @@ __metadata: languageName: node linkType: hard -"eslint-scope@npm:^7.1.1": - version: 7.1.1 - resolution: "eslint-scope@npm:7.1.1" +"eslint-scope@npm:^7.2.2": + version: 7.2.2 + resolution: "eslint-scope@npm:7.2.2" dependencies: esrecurse: ^4.3.0 estraverse: ^5.2.0 - checksum: 9f6e974ab2db641ca8ab13508c405b7b859e72afe9f254e8131ff154d2f40c99ad4545ce326fd9fde3212ff29707102562a4834f1c48617b35d98c71a97fbf3e + checksum: ec97dbf5fb04b94e8f4c5a91a7f0a6dd3c55e46bfc7bbcd0e3138c3a76977570e02ed89a1810c778dcd72072ff0e9621ba1379b4babe53921d71e2e4486fda3e languageName: node linkType: hard -"eslint-visitor-keys@npm:^3.3.0, eslint-visitor-keys@npm:^3.4.0": - version: 3.4.0 - resolution: "eslint-visitor-keys@npm:3.4.0" - checksum: 33159169462d3989321a1ec1e9aaaf6a24cc403d5d347e9886d1b5bfe18ffa1be73bdc6203143a28a606b142b1af49787f33cff0d6d0813eb5f2e8d2e1a6043c +"eslint-visitor-keys@npm:^3.3.0, eslint-visitor-keys@npm:^3.4.1, eslint-visitor-keys@npm:^3.4.2": + version: 3.4.2 + resolution: "eslint-visitor-keys@npm:3.4.2" + checksum: 9e0e7e4aaea705c097ae37c97410e5f167d4d2193be2edcb1f0760762ede3df01545e4820ae314f42dcec687745f2c6dcaf6d83575c4a2a241eb0c8517d724f2 languageName: node linkType: hard "eslint@npm:^8.15.0": - version: 8.37.0 - resolution: "eslint@npm:8.37.0" + version: 8.46.0 + resolution: "eslint@npm:8.46.0" dependencies: "@eslint-community/eslint-utils": ^4.2.0 - "@eslint-community/regexpp": ^4.4.0 - "@eslint/eslintrc": ^2.0.2 - "@eslint/js": 8.37.0 - "@humanwhocodes/config-array": ^0.11.8 + "@eslint-community/regexpp": ^4.6.1 + "@eslint/eslintrc": ^2.1.1 + "@eslint/js": ^8.46.0 + "@humanwhocodes/config-array": ^0.11.10 "@humanwhocodes/module-importer": ^1.0.1 "@nodelib/fs.walk": ^1.2.8 - ajv: ^6.10.0 + ajv: ^6.12.4 chalk: ^4.0.0 cross-spawn: ^7.0.2 debug: ^4.3.2 doctrine: ^3.0.0 escape-string-regexp: ^4.0.0 - eslint-scope: ^7.1.1 - eslint-visitor-keys: ^3.4.0 - espree: ^9.5.1 + eslint-scope: ^7.2.2 + eslint-visitor-keys: ^3.4.2 + espree: ^9.6.1 esquery: ^1.4.2 esutils: ^2.0.2 fast-deep-equal: ^3.1.3 @@ -3990,26 +3337,23 @@ __metadata: find-up: ^5.0.0 glob-parent: ^6.0.2 globals: ^13.19.0 - grapheme-splitter: ^1.0.4 + graphemer: ^1.4.0 ignore: ^5.2.0 - import-fresh: ^3.0.0 imurmurhash: ^0.1.4 is-glob: ^4.0.0 is-path-inside: ^3.0.3 - js-sdsl: ^4.1.4 js-yaml: ^4.1.0 json-stable-stringify-without-jsonify: ^1.0.1 levn: ^0.4.1 lodash.merge: ^4.6.2 minimatch: ^3.1.2 natural-compare: ^1.4.0 - optionator: ^0.9.1 + optionator: ^0.9.3 strip-ansi: ^6.0.1 - strip-json-comments: ^3.1.0 text-table: ^0.2.0 bin: eslint: bin/eslint.js - checksum: 80f3d5cdce2d671f4794e392d234a78d039c347673defb0596268bd481e8f30a53d93c01ff4f66a546c87d97ab4122c0e9cafe1371f87cb03cee6b7d5aa97595 + checksum: 7a7d36b1a3bbc12e08fbb5bc36fd482a7a5a1797e62e762499dd45601b9e45aaa53a129f31ce0b4444551a9639b8b681ad535f379893dd1e3ae37b31dccd82aa languageName: node linkType: hard @@ -4020,14 +3364,14 @@ __metadata: languageName: node linkType: hard -"espree@npm:^9.5.1": - version: 9.5.1 - resolution: "espree@npm:9.5.1" +"espree@npm:^9.6.0, espree@npm:^9.6.1": + version: 9.6.1 + resolution: "espree@npm:9.6.1" dependencies: - acorn: ^8.8.0 + acorn: ^8.9.0 acorn-jsx: ^5.3.2 - eslint-visitor-keys: ^3.4.0 - checksum: cdf6e43540433d917c4f2ee087c6e987b2063baa85a1d9cdaf51533d78275ebd5910c42154e7baf8e3e89804b386da0a2f7fad2264d8f04420e7506bf87b3b88 + eslint-visitor-keys: ^3.4.1 + checksum: eb8c149c7a2a77b3f33a5af80c10875c3abd65450f60b8af6db1bfcfa8f101e21c1e56a561c6dc13b848e18148d43469e7cd208506238554fb5395a9ea5a1ab9 languageName: node linkType: hard @@ -4104,6 +3448,13 @@ __metadata: languageName: node linkType: hard +"exponential-backoff@npm:^3.1.1": + version: 3.1.1 + resolution: "exponential-backoff@npm:3.1.1" + checksum: 3d21519a4f8207c99f7457287291316306255a328770d320b401114ec8481986e4e467e854cb9914dd965e0a1ca810a23ccb559c642c88f4c7f55c55778a9b48 + 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" @@ -4112,15 +3463,15 @@ __metadata: linkType: hard "fast-glob@npm:^3.2.9": - version: 3.2.12 - resolution: "fast-glob@npm:3.2.12" + version: 3.3.1 + resolution: "fast-glob@npm:3.3.1" 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.4 - checksum: 0b1990f6ce831c7e28c4d505edcdaad8e27e88ab9fa65eedadb730438cfc7cde4910d6c975d6b7b8dc8a73da4773702ebcfcd6e3518e73938bb1383badfe01c2 + checksum: b6f3add6403e02cf3a798bfbb1183d0f6da2afd368f27456010c0bc1f9640aea308243d4cb2c0ab142f618276e65ecb8be1661d7c62a7b4e5ba774b9ce5432e5 languageName: node linkType: hard @@ -4138,17 +3489,6 @@ __metadata: languageName: node linkType: hard -"fast-xml-parser@npm:4.1.2": - version: 4.1.2 - resolution: "fast-xml-parser@npm:4.1.2" - dependencies: - strnum: ^1.0.5 - bin: - fxparser: src/cli/cli.js - checksum: 6a7d1b17057f8470e70603eddfa75f990625735d068d57ece861d0154ad8d27fda63c2831d07e1ecd7e68e993738b2448925cb9277d8c0ed68009623bbcd63c6 - languageName: node - linkType: hard - "fast-xml-parser@npm:4.2.5": version: 4.2.5 resolution: "fast-xml-parser@npm:4.2.5" @@ -4278,6 +3618,16 @@ __metadata: languageName: node linkType: hard +"foreground-child@npm:^3.1.0": + version: 3.1.1 + resolution: "foreground-child@npm:3.1.1" + dependencies: + cross-spawn: ^7.0.0 + signal-exit: ^4.0.1 + checksum: 139d270bc82dc9e6f8bc045fe2aae4001dc2472157044fdfad376d0a3457f77857fa883c1c8b21b491c6caade9a926a4bed3d3d2e8d3c9202b151a4cbbd0bcd5 + languageName: node + linkType: hard + "form-data@npm:^4.0.0": version: 4.0.0 resolution: "form-data@npm:4.0.0" @@ -4303,7 +3653,7 @@ __metadata: languageName: node linkType: hard -"fs-minipass@npm:^2.0.0, fs-minipass@npm:^2.1.0": +"fs-minipass@npm:^2.0.0": version: 2.1.0 resolution: "fs-minipass@npm:2.1.0" dependencies: @@ -4312,6 +3662,15 @@ __metadata: languageName: node linkType: hard +"fs-minipass@npm:^3.0.0": + version: 3.0.2 + resolution: "fs-minipass@npm:3.0.2" + dependencies: + minipass: ^5.0.0 + checksum: e9cc0e1f2d01c6f6f62f567aee59530aba65c6c7b2ae88c5027bc34c711ebcfcfaefd0caf254afa6adfe7d1fba16bc2537508a6235196bac7276747d078aef0a + languageName: node + linkType: hard + "fs.realpath@npm:^1.0.0": version: 1.0.0 resolution: "fs.realpath@npm:1.0.0" @@ -4383,13 +3742,14 @@ __metadata: linkType: hard "get-intrinsic@npm:^1.0.2": - version: 1.2.0 - resolution: "get-intrinsic@npm:1.2.0" + version: 1.2.1 + resolution: "get-intrinsic@npm:1.2.1" dependencies: function-bind: ^1.1.1 has: ^1.0.3 + has-proto: ^1.0.1 has-symbols: ^1.0.3 - checksum: 78fc0487b783f5c58cf2dccafc3ae656ee8d2d8062a8831ce4a95e7057af4587a1d4882246c033aca0a7b4965276f4802b45cc300338d1b77a73d3e3e3f4877d + checksum: 5b61d88552c24b0cf6fa2d1b3bc5459d7306f699de060d76442cce49a4721f52b8c560a33ab392cf5575b7810277d54ded9d4d39a1ea61855619ebc005aa7e5f languageName: node linkType: hard @@ -4446,6 +3806,21 @@ __metadata: languageName: node linkType: hard +"glob@npm:^10.2.2": + version: 10.3.3 + resolution: "glob@npm:10.3.3" + dependencies: + foreground-child: ^3.1.0 + jackspeak: ^2.0.3 + minimatch: ^9.0.1 + minipass: ^5.0.0 || ^6.0.2 || ^7.0.0 + path-scurry: ^1.10.1 + bin: + glob: dist/cjs/src/bin.js + checksum: 29190d3291f422da0cb40b77a72fc8d2c51a36524e99b8bf412548b7676a6627489528b57250429612b6eec2e6fe7826d328451d3e694a9d15e575389308ec53 + languageName: node + linkType: hard + "glob@npm:^7.1.3, glob@npm:^7.1.4, glob@npm:^7.1.6": version: 7.2.3 resolution: "glob@npm:7.2.3" @@ -4460,19 +3835,6 @@ __metadata: languageName: node linkType: hard -"glob@npm:^8.0.1": - version: 8.1.0 - resolution: "glob@npm:8.1.0" - dependencies: - fs.realpath: ^1.0.0 - inflight: ^1.0.4 - inherits: 2 - minimatch: ^5.0.1 - once: ^1.3.0 - checksum: 92fbea3221a7d12075f26f0227abac435de868dd0736a17170663783296d0dd8d3d532a5672b4488a439bf5d7fb85cdd07c11185d6cd39184f0385cbdfb86a47 - languageName: node - linkType: hard - "globals@npm:^11.1.0": version: 11.12.0 resolution: "globals@npm:11.12.0" @@ -4510,19 +3872,19 @@ __metadata: languageName: node linkType: hard -"grapheme-splitter@npm:^1.0.4": - version: 1.0.4 - resolution: "grapheme-splitter@npm:1.0.4" - checksum: 0c22ec54dee1b05cd480f78cf14f732cb5b108edc073572c4ec205df4cd63f30f8db8025afc5debc8835a8ddeacf648a1c7992fe3dcd6ad38f9a476d84906620 +"graphemer@npm:^1.4.0": + version: 1.4.0 + resolution: "graphemer@npm:1.4.0" + checksum: bab8f0be9b568857c7bec9fda95a89f87b783546d02951c40c33f84d05bb7da3fd10f863a9beb901463669b6583173a8c8cc6d6b306ea2b9b9d5d3d943c3a673 languageName: node linkType: hard "handlebars@npm:^4.7.7": - version: 4.7.7 - resolution: "handlebars@npm:4.7.7" + version: 4.7.8 + resolution: "handlebars@npm:4.7.8" dependencies: minimist: ^1.2.5 - neo-async: ^2.6.0 + neo-async: ^2.6.2 source-map: ^0.6.1 uglify-js: ^3.1.4 wordwrap: ^1.0.0 @@ -4531,7 +3893,7 @@ __metadata: optional: true bin: handlebars: bin/handlebars - checksum: 1e79a43f5e18d15742977cb987923eab3e2a8f44f2d9d340982bcb69e1735ed049226e534d7c1074eaddaf37e4fb4f471a8adb71cddd5bc8cf3f894241df5cee + checksum: 00e68bb5c183fd7b8b63322e6234b5ac8fbb960d712cb3f25587d559c2951d9642df83c04a1172c918c41bcfc81bfbd7a7718bbce93b893e0135fc99edea93ff languageName: node linkType: hard @@ -4549,6 +3911,13 @@ __metadata: languageName: node linkType: hard +"has-proto@npm:^1.0.1": + version: 1.0.1 + resolution: "has-proto@npm:1.0.1" + checksum: febc5b5b531de8022806ad7407935e2135f1cc9e64636c3916c6842bd7995994ca3b29871ecd7954bd35f9e2986c17b3b227880484d22259e2f8e6ce63fd383e + languageName: node + linkType: hard + "has-symbols@npm:^1.0.2, has-symbols@npm:^1.0.3": version: 1.0.3 resolution: "has-symbols@npm:1.0.3" @@ -4624,7 +3993,7 @@ __metadata: languageName: node linkType: hard -"http-cache-semantics@npm:^4.1.0": +"http-cache-semantics@npm:^4.1.1": version: 4.1.1 resolution: "http-cache-semantics@npm:4.1.1" checksum: 83ac0bc60b17a3a36f9953e7be55e5c8f41acc61b22583060e8dedc9dd5e3607c823a88d0926f9150e571f90946835c7fe150732801010845c72cd8bbff1a236 @@ -4735,7 +4104,7 @@ __metadata: languageName: node linkType: hard -"import-fresh@npm:^3.0.0, import-fresh@npm:^3.2.1": +"import-fresh@npm:^3.2.1": version: 3.3.0 resolution: "import-fresh@npm:3.3.0" dependencies: @@ -4759,13 +4128,6 @@ __metadata: languageName: node linkType: hard -"infer-owner@npm:^1.0.4": - version: 1.0.4 - resolution: "infer-owner@npm:1.0.4" - checksum: 181e732764e4a0611576466b4b87dac338972b839920b2a8cde43642e4ed6bd54dc1fb0b40874728f2a2df9a1b097b8ff83b56d5f8f8e3927f837fdcb47d8a89 - languageName: node - linkType: hard - "inflight@npm:^1.0.4": version: 1.0.6 resolution: "inflight@npm:1.0.6" @@ -4836,15 +4198,6 @@ __metadata: languageName: node linkType: hard -"is-core-module@npm:^2.9.0": - version: 2.11.0 - resolution: "is-core-module@npm:2.11.0" - dependencies: - has: ^1.0.3 - checksum: f96fd490c6b48eb4f6d10ba815c6ef13f410b0ba6f7eb8577af51697de523e5f2cd9de1c441b51d27251bf0e4aebc936545e33a5d26d5d51f28d25698d4a8bab - languageName: node - linkType: hard - "is-extglob@npm:^2.1.1": version: 2.1.1 resolution: "is-extglob@npm:2.1.1" @@ -5004,13 +4357,13 @@ __metadata: linkType: hard "istanbul-lib-report@npm:^3.0.0": - version: 3.0.0 - resolution: "istanbul-lib-report@npm:3.0.0" + version: 3.0.1 + resolution: "istanbul-lib-report@npm:3.0.1" dependencies: istanbul-lib-coverage: ^3.0.0 - make-dir: ^3.0.0 + make-dir: ^4.0.0 supports-color: ^7.1.0 - checksum: 3f29eb3f53c59b987386e07fe772d24c7f58c6897f34c9d7a296f4000de7ae3de9eb95c3de3df91dc65b134c84dee35c54eee572a56243e8907c48064e34ff1b + checksum: fd17a1b879e7faf9bb1dc8f80b2a16e9f5b7b8498fe6ed580a618c34df0bfe53d2abd35bf8a0a00e628fb7405462576427c7df20bbe4148d19c14b431c974b21 languageName: node linkType: hard @@ -5026,12 +4379,25 @@ __metadata: linkType: hard "istanbul-reports@npm:^3.0.2": - version: 3.1.5 - resolution: "istanbul-reports@npm:3.1.5" + version: 3.1.6 + resolution: "istanbul-reports@npm:3.1.6" dependencies: html-escaper: ^2.0.0 istanbul-lib-report: ^3.0.0 - checksum: 7867228f83ed39477b188ea07e7ccb9b4f5320b6f73d1db93a0981b7414fa4ef72d3f80c4692c442f90fc250d9406e71d8d7ab65bb615cb334e6292b73192b89 + checksum: 44c4c0582f287f02341e9720997f9e82c071627e1e862895745d5f52ec72c9b9f38e1d12370015d2a71dcead794f34c7732aaef3fab80a24bc617a21c3d911d6 + languageName: node + linkType: hard + +"jackspeak@npm:^2.0.3": + version: 2.2.2 + resolution: "jackspeak@npm:2.2.2" + dependencies: + "@isaacs/cliui": ^8.0.2 + "@pkgjs/parseargs": ^0.11.0 + dependenciesMeta: + "@pkgjs/parseargs": + optional: true + checksum: 7b1468dd910afc00642db87448f24b062346570b8b47531409aa9012bcb95fdf7ec2b1c48edbb8b57a938c08391f8cc01b5034fc335aa3a2e74dbcc0ee5c555a languageName: node linkType: hard @@ -5042,13 +4408,6 @@ __metadata: languageName: node linkType: hard -"js-sdsl@npm:^4.1.4": - version: 4.4.0 - resolution: "js-sdsl@npm:4.4.0" - checksum: 7bb08a2d746ab7ff742720339aa006c631afe05e77d11eda988c1c35fae8e03e492e4e347e883e786e3ce6170685d4780c125619111f0730c11fdb41b04059c7 - languageName: node - linkType: hard - "js-tokens@npm:^4.0.0": version: 4.0.0 resolution: "js-tokens@npm:4.0.0" @@ -5119,14 +4478,14 @@ __metadata: linkType: hard "jsonwebtoken@npm:^9.0.0": - version: 9.0.0 - resolution: "jsonwebtoken@npm:9.0.0" + version: 9.0.1 + resolution: "jsonwebtoken@npm:9.0.1" dependencies: jws: ^3.2.2 lodash: ^4.17.21 ms: ^2.1.1 semver: ^7.3.8 - checksum: b9181cecf9df99f1dc0253f91ba000a1aa4d91f5816d1608c0dba61a5623726a0bfe200b51df25de18c1a6000825d231ad7ce2788aa54fd48dcb760ad9eb9514 + checksum: 0eafe268896f4e8f9ab1f0f20e8c645721b7a9cddc41c0aba1e58da5c34564e8c9990817c1a5b646d795bcbb1339350826fe57c4569b5379ba9eea4a9aa5bbd0 languageName: node linkType: hard @@ -5167,46 +4526,7 @@ __metadata: languageName: node linkType: hard -"knex@npm:*": - version: 2.4.2 - resolution: "knex@npm:2.4.2" - dependencies: - colorette: 2.0.19 - commander: ^9.1.0 - debug: 4.3.4 - escalade: ^3.1.1 - esm: ^3.2.25 - get-package-type: ^0.1.0 - getopts: 2.3.0 - interpret: ^2.2.0 - lodash: ^4.17.21 - pg-connection-string: 2.5.0 - rechoir: ^0.8.0 - resolve-from: ^5.0.0 - tarn: ^3.0.2 - tildify: 2.0.0 - peerDependenciesMeta: - better-sqlite3: - optional: true - mysql: - optional: true - mysql2: - optional: true - pg: - optional: true - pg-native: - optional: true - sqlite3: - optional: true - tedious: - optional: true - bin: - knex: bin/cli.js - checksum: cfb6436c0e3df3cd1f55d3637e4f222d1acbc7b6ec3757e52c92cbf0a7da4055d40156d707ffede57dc155af75d7f5739a49d1b31d8755bc77bc0e4e2a570748 - languageName: node - linkType: hard - -"knex@npm:^2.5.1": +"knex@npm:*, knex@npm:^2.5.1": version: 2.5.1 resolution: "knex@npm:2.5.1" dependencies: @@ -5527,12 +4847,19 @@ __metadata: languageName: node linkType: hard -"magic-string@npm:^0.25.7": - version: 0.25.9 - resolution: "magic-string@npm:0.25.9" +"lru-cache@npm:^9.1.1 || ^10.0.0": + version: 10.0.0 + resolution: "lru-cache@npm:10.0.0" + checksum: 18f101675fe283bc09cda0ef1e3cc83781aeb8373b439f086f758d1d91b28730950db785999cd060d3c825a8571c03073e8c14512b6655af2188d623031baf50 + languageName: node + linkType: hard + +"magic-string@npm:^0.30.0": + version: 0.30.2 + resolution: "magic-string@npm:0.30.2" dependencies: - sourcemap-codec: ^1.4.8 - checksum: 9a0e55a15c7303fc360f9572a71cffba1f61451bc92c5602b1206c9d17f492403bf96f946dfce7483e66822d6b74607262e24392e87b0ac27b786e69a40e9b1a + "@jridgewell/sourcemap-codec": ^1.4.15 + checksum: c0bbb9b27b2772e6bfaa5d0f6452d47c462d588ae7c43fbaac062b07836d3ec0140fcdd42a57aa53ed990abafcdd0fc17907813921b5df04eccf43e67674bc57 languageName: node linkType: hard @@ -5545,6 +4872,15 @@ __metadata: languageName: node linkType: hard +"make-dir@npm:^4.0.0": + version: 4.0.0 + resolution: "make-dir@npm:4.0.0" + dependencies: + semver: ^7.5.3 + checksum: bf0731a2dd3aab4db6f3de1585cea0b746bb73eb5a02e3d8d72757e376e64e6ada190b1eddcde5b2f24a81b688a9897efd5018737d05e02e2a671dda9cff8a8a + languageName: node + linkType: hard + "make-error@npm:^1.1.1": version: 1.3.6 resolution: "make-error@npm:1.3.6" @@ -5552,27 +4888,33 @@ __metadata: languageName: node linkType: hard -"make-fetch-happen@npm:^10.0.3": - version: 10.2.1 - resolution: "make-fetch-happen@npm:10.2.1" +"make-fetch-happen@npm:^11.0.3": + version: 11.1.1 + resolution: "make-fetch-happen@npm:11.1.1" dependencies: agentkeepalive: ^4.2.1 - cacache: ^16.1.0 - http-cache-semantics: ^4.1.0 + cacache: ^17.0.0 + http-cache-semantics: ^4.1.1 http-proxy-agent: ^5.0.0 https-proxy-agent: ^5.0.0 is-lambda: ^1.0.1 lru-cache: ^7.7.1 - minipass: ^3.1.6 - minipass-collect: ^1.0.2 - minipass-fetch: ^2.0.3 + minipass: ^5.0.0 + minipass-fetch: ^3.0.0 minipass-flush: ^1.0.5 minipass-pipeline: ^1.2.4 negotiator: ^0.6.3 promise-retry: ^2.0.1 socks-proxy-agent: ^7.0.0 - ssri: ^9.0.0 - checksum: 2332eb9a8ec96f1ffeeea56ccefabcb4193693597b132cd110734d50f2928842e22b84cfa1508e921b8385cdfd06dda9ad68645fed62b50fff629a580f5fb72c + ssri: ^10.0.0 + checksum: 7268bf274a0f6dcf0343829489a4506603ff34bd0649c12058753900b0eb29191dce5dba12680719a5d0a983d3e57810f594a12f3c18494e93a1fbc6348a4540 + languageName: node + linkType: hard + +"mandrill-api@npm:^1.0.45": + version: 1.0.45 + resolution: "mandrill-api@npm:1.0.45" + checksum: 25803951a1113cb597c2ced36596f9f10e43831e5b219c3b93c73b5567362e802a65256665f4cf138f89a60f0c3bb155ba839d6cb24111eeb21c3ebc90a38607 languageName: node linkType: hard @@ -5662,12 +5004,12 @@ __metadata: languageName: node linkType: hard -"minimatch@npm:^5.0.1": - version: 5.1.6 - resolution: "minimatch@npm:5.1.6" +"minimatch@npm:^9.0.1": + version: 9.0.3 + resolution: "minimatch@npm:9.0.3" dependencies: brace-expansion: ^2.0.1 - checksum: 7564208ef81d7065a370f788d337cd80a689e981042cb9a1d0e6580b6c6a8c9279eba80010516e258835a988363f99f54a6f711a315089b8b42694f5da9d0d77 + checksum: 253487976bf485b612f16bf57463520a14f512662e592e95c571afdab1442a6a6864b6c88f248ce6fc4ff0b6de04ac7aa6c8bb51e868e99d1d65eb0658a708b5 languageName: node linkType: hard @@ -5687,18 +5029,18 @@ __metadata: languageName: node linkType: hard -"minipass-fetch@npm:^2.0.3": - version: 2.1.2 - resolution: "minipass-fetch@npm:2.1.2" +"minipass-fetch@npm:^3.0.0": + version: 3.0.3 + resolution: "minipass-fetch@npm:3.0.3" dependencies: encoding: ^0.1.13 - minipass: ^3.1.6 + minipass: ^5.0.0 minipass-sized: ^1.0.3 minizlib: ^2.1.2 dependenciesMeta: encoding: optional: true - checksum: 3f216be79164e915fc91210cea1850e488793c740534985da017a4cbc7a5ff50506956d0f73bb0cb60e4fe91be08b6b61ef35101706d3ef5da2c8709b5f08f91 + checksum: af5ab2552a16fcf505d35fd7ffb84b57f4a0eeb269e6e1d9a2a75824dda48b36e527083250b7cca4a4def21d9544e2ade441e4730e233c0bc2133f6abda31e18 languageName: node linkType: hard @@ -5729,7 +5071,7 @@ __metadata: languageName: node linkType: hard -"minipass@npm:^3.0.0, minipass@npm:^3.1.1, minipass@npm:^3.1.6": +"minipass@npm:^3.0.0": version: 3.3.6 resolution: "minipass@npm:3.3.6" dependencies: @@ -5738,10 +5080,17 @@ __metadata: languageName: node linkType: hard -"minipass@npm:^4.0.0": - version: 4.2.5 - resolution: "minipass@npm:4.2.5" - checksum: 4f9c19af23a5d4a9e7156feefc9110634b178a8cff8f8271af16ec5ebf7e221725a97429952c856f5b17b30c2065ebd24c81722d90c93d2122611d75b952b48f +"minipass@npm:^5.0.0": + version: 5.0.0 + resolution: "minipass@npm:5.0.0" + checksum: 425dab288738853fded43da3314a0b5c035844d6f3097a8e3b5b29b328da8f3c1af6fc70618b32c29ff906284cf6406b6841376f21caaadd0793c1d5a6a620ea + languageName: node + linkType: hard + +"minipass@npm:^5.0.0 || ^6.0.2 || ^7.0.0": + version: 7.0.2 + resolution: "minipass@npm:7.0.2" + checksum: 46776de732eb7cef2c7404a15fb28c41f5c54a22be50d47b03c605bf21f5c18d61a173c0a20b49a97e7a65f78d887245066410642551e45fffe04e9ac9e325bc languageName: node linkType: hard @@ -5755,7 +5104,7 @@ __metadata: languageName: node linkType: hard -"mkdirp@npm:^1.0.3, mkdirp@npm:^1.0.4": +"mkdirp@npm:^1.0.3": version: 1.0.4 resolution: "mkdirp@npm:1.0.4" bin: @@ -5819,7 +5168,7 @@ __metadata: languageName: node linkType: hard -"nanoid@npm:^3.3.4": +"nanoid@npm:^3.3.6": version: 3.3.6 resolution: "nanoid@npm:3.3.6" bin: @@ -5849,7 +5198,7 @@ __metadata: languageName: node linkType: hard -"neo-async@npm:^2.6.0": +"neo-async@npm:^2.6.2": version: 2.6.2 resolution: "neo-async@npm:2.6.2" checksum: deac9f8d00eda7b2e5cd1b2549e26e10a0faa70adaa6fdadca701cc55f49ee9018e427f424bac0c790b7c7e2d3068db97f3093f1093975f2acb8f8818b936ed9 @@ -5870,13 +5219,14 @@ __metadata: linkType: hard "node-gyp@npm:latest": - version: 9.3.1 - resolution: "node-gyp@npm:9.3.1" + version: 9.4.0 + resolution: "node-gyp@npm:9.4.0" dependencies: env-paths: ^2.2.0 + exponential-backoff: ^3.1.1 glob: ^7.1.4 graceful-fs: ^4.2.6 - make-fetch-happen: ^10.0.3 + make-fetch-happen: ^11.0.3 nopt: ^6.0.0 npmlog: ^6.0.0 rimraf: ^3.0.2 @@ -5885,7 +5235,7 @@ __metadata: which: ^2.0.2 bin: node-gyp: bin/node-gyp.js - checksum: b860e9976fa645ca0789c69e25387401b4396b93c8375489b5151a6c55cf2640a3b6183c212b38625ef7c508994930b72198338e3d09b9d7ade5acc4aaf51ea7 + checksum: 78b404e2e0639d64e145845f7f5a3cb20c0520cdaf6dda2f6e025e9b644077202ea7de1232396ba5bde3fee84cdc79604feebe6ba3ec84d464c85d407bb5da99 languageName: node linkType: hard @@ -5898,30 +5248,30 @@ __metadata: languageName: node linkType: hard -"node-releases@npm:^2.0.8": - version: 2.0.10 - resolution: "node-releases@npm:2.0.10" - checksum: d784ecde25696a15d449c4433077f5cce620ed30a1656c4abf31282bfc691a70d9618bae6868d247a67914d1be5cc4fde22f65a05f4398cdfb92e0fc83cadfbc +"node-releases@npm:^2.0.13": + version: 2.0.13 + resolution: "node-releases@npm:2.0.13" + checksum: 17ec8f315dba62710cae71a8dad3cd0288ba943d2ece43504b3b1aa8625bf138637798ab470b1d9035b0545996f63000a8a926e0f6d35d0996424f8b6d36dda3 languageName: node linkType: hard -"nodemon@npm:^2.0.16": - version: 2.0.22 - resolution: "nodemon@npm:2.0.22" +"nodemon@npm:^3.0.1": + version: 3.0.1 + resolution: "nodemon@npm:3.0.1" dependencies: chokidar: ^3.5.2 debug: ^3.2.7 ignore-by-default: ^1.0.1 minimatch: ^3.1.2 pstree.remy: ^1.1.8 - semver: ^5.7.1 - simple-update-notifier: ^1.0.7 + semver: ^7.5.3 + simple-update-notifier: ^2.0.0 supports-color: ^5.5.0 touch: ^3.1.0 undefsafe: ^2.0.5 bin: nodemon: bin/nodemon.js - checksum: 9c987e139748f5b5c480c6c9080bdc97304ee7d29172b7b3da1a7db590b1323ad57b96346304e9b522b0e445c336dc393ccd3f9f45c73b20d476d2347890dcd0 + checksum: 6a5d81855760d6617049eccce10ccf02bddb482dab13ceea5280ae595ec7004eee13e7b934368e3f46c37fe4d970342a8c38c99cae7e93e4d7a3ed1c1ecb6acf languageName: node linkType: hard @@ -6074,17 +5424,17 @@ __metadata: languageName: node linkType: hard -"optionator@npm:^0.9.1": - version: 0.9.1 - resolution: "optionator@npm:0.9.1" +"optionator@npm:^0.9.3": + version: 0.9.3 + resolution: "optionator@npm:0.9.3" dependencies: + "@aashutoshrathi/word-wrap": ^1.2.3 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.3 - checksum: dbc6fa065604b24ea57d734261914e697bd73b69eff7f18e967e8912aa2a40a19a9f599a507fa805be6c13c24c4eae8c71306c239d517d42d4c041c942f508a0 + checksum: 09281999441f2fe9c33a5eeab76700795365a061563d66b098923eb719251a42bdbe432790d35064d0816ead9296dbeb1ad51a733edf4167c96bd5d0882e428a languageName: node linkType: hard @@ -6252,6 +5602,16 @@ __metadata: languageName: node linkType: hard +"path-scurry@npm:^1.10.1": + version: 1.10.1 + resolution: "path-scurry@npm:1.10.1" + dependencies: + lru-cache: ^9.1.1 || ^10.0.0 + minipass: ^5.0.0 || ^6.0.2 || ^7.0.0 + checksum: e2557cff3a8fb8bc07afdd6ab163a92587884f9969b05bbbaf6fe7379348bfb09af9ed292af12ed32398b15fb443e81692047b786d1eeb6d898a51eb17ed7d90 + languageName: node + linkType: hard + "path-to-regexp@npm:^1.7.0": version: 1.8.0 resolution: "path-to-regexp@npm:1.8.0" @@ -6286,7 +5646,7 @@ __metadata: version: 0.0.0-use.local resolution: "payment-service@workspace:." dependencies: - "@alexsasharegan/simple-cache": ^3.3.3 + "@ardrive/ardrive-promise-cache": ^1.1.4 "@aws-sdk/client-secrets-manager": ^3.290.0 "@aws-sdk/client-ssm": ^3.369.0 "@istanbuljs/nyc-config-typescript": ^1.0.2 @@ -6300,10 +5660,12 @@ __metadata: "@types/koa": ^2.13.4 "@types/koa-router": ^7.4.4 "@types/koa__cors": ^3.3.0 + "@types/mandrill-api": ^1.0.33 "@types/mocha": ^9.1.1 "@types/node": ^18.16.1 "@types/sinon": ^10.0.11 "@types/sinon-chai": ^3.2.9 + "@types/validator": ^13.11.7 "@typescript-eslint/eslint-plugin": ^5.25.0 "@typescript-eslint/parser": ^5.25.0 arweave: ^1.13.4 @@ -6323,8 +5685,9 @@ __metadata: koa-router: 11.0.1 koa2-swagger-ui: ^5.8.0 lint-staged: ^12.5.0 + mandrill-api: ^1.0.45 mocha: ^10.0.0 - nodemon: ^2.0.16 + nodemon: ^3.0.1 nyc: ^15.1.0 p-limit: ^3.1.0 pg: ^8.8.0 @@ -6337,15 +5700,16 @@ __metadata: stripe: ^11.13.0 ts-node: ^10.7.0 typescript: ^4.7.4 + validator: ^13.11.0 winston: ^3.8.2 yaml: ^2.2.2 languageName: unknown linkType: soft -"pg-connection-string@npm:2.5.0, pg-connection-string@npm:^2.5.0": - version: 2.5.0 - resolution: "pg-connection-string@npm:2.5.0" - checksum: a6f3a068f7c9416a5b33a326811caf0dfaaee045c225b7c628b4c9b4e9a2b25bdd12a21e4c48940e1000ea223a4e608ca122d2ff3dd08c8b1db0fc9f5705133a +"pg-cloudflare@npm:^1.1.1": + version: 1.1.1 + resolution: "pg-cloudflare@npm:1.1.1" + checksum: 32aac06b5dc4588bbf78801b6267781bc7e13be672009df949d08e9627ba9fdc26924916665d4de99d47f9b0495301930547488dad889d826856976c7b3f3731 languageName: node linkType: hard @@ -6356,6 +5720,13 @@ __metadata: languageName: node linkType: hard +"pg-connection-string@npm:^2.6.2": + version: 2.6.2 + resolution: "pg-connection-string@npm:2.6.2" + checksum: 22265882c3b6f2320785378d0760b051294a684989163d5a1cde4009e64e84448d7bf67d9a7b9e7f69440c3ee9e2212f9aa10dd17ad6773f6143c6020cebbcb5 + languageName: node + linkType: hard + "pg-int8@npm:1.0.1": version: 1.0.1 resolution: "pg-int8@npm:1.0.1" @@ -6363,12 +5734,12 @@ __metadata: languageName: node linkType: hard -"pg-pool@npm:^3.6.0": - version: 3.6.0 - resolution: "pg-pool@npm:3.6.0" +"pg-pool@npm:^3.6.1": + version: 3.6.1 + resolution: "pg-pool@npm:3.6.1" peerDependencies: pg: ">=8.0" - checksum: f3fe050fbfe27406369340c4c26efcbe21a388ace085a876453de0ea496a315c38b2dc739ac97d4767a359e911da2ec4810467f72601eeec8ad540e58b27987c + checksum: 8a6513e6f74a794708c9dd16d2ccda0debadc56435ec2582de2b2e35b01315550c5dab8a0a9a2a16f4adce45523228f5739940fb7687ec7e9c300f284eb08fd1 languageName: node linkType: hard @@ -6393,22 +5764,26 @@ __metadata: linkType: hard "pg@npm:^8.8.0": - version: 8.10.0 - resolution: "pg@npm:8.10.0" + version: 8.11.2 + resolution: "pg@npm:8.11.2" dependencies: buffer-writer: 2.0.0 packet-reader: 1.0.0 - pg-connection-string: ^2.5.0 - pg-pool: ^3.6.0 + pg-cloudflare: ^1.1.1 + pg-connection-string: ^2.6.2 + pg-pool: ^3.6.1 pg-protocol: ^1.6.0 pg-types: ^2.1.0 pgpass: 1.x peerDependencies: pg-native: ">=3.0.1" + dependenciesMeta: + pg-cloudflare: + optional: true peerDependenciesMeta: pg-native: optional: true - checksum: c6be78f2e823f2ae3c618c8e54a6622592dd71b556fb665d7eaedcbcc2fa5d210a8bcf519401e72526a65b9d797f19b772f48f29b9d9f31e98dd526fd27d61e0 + checksum: dfea8a2269d500dee8c17291207e5a25897163480037beb7a59be35f51e33b519c297c943ea6898b285d6a74a0d661901dc9cff2e587cc4be0bbf09b833a71a5 languageName: node linkType: hard @@ -6454,13 +5829,13 @@ __metadata: linkType: hard "postcss@npm:^8.1.10": - version: 8.4.21 - resolution: "postcss@npm:8.4.21" + version: 8.4.27 + resolution: "postcss@npm:8.4.27" dependencies: - nanoid: ^3.3.4 + nanoid: ^3.3.6 picocolors: ^1.0.0 source-map-js: ^1.0.2 - checksum: e39ac60ccd1542d4f9d93d894048aac0d686b3bb38e927d8386005718e6793dbbb46930f0a523fe382f1bbd843c6d980aaea791252bf5e176180e5a4336d9679 + checksum: 1cdd0c298849df6cd65f7e646a3ba36870a37b65f55fd59d1a165539c263e9b4872a402bf4ed1ca1bc31f58b68b2835545e33ea1a23b161a1f8aa6d5ded81e78 languageName: node linkType: hard @@ -6502,11 +5877,11 @@ __metadata: linkType: hard "prettier@npm:^2.6.2": - version: 2.8.7 - resolution: "prettier@npm:2.8.7" + version: 2.8.8 + resolution: "prettier@npm:2.8.8" bin: prettier: bin-prettier.js - checksum: fdc8f2616f099f5f0d685907f4449a70595a0fc1d081a88919604375989e0d5e9168d6121d8cc6861f21990b31665828e00472544d785d5940ea08a17660c3a6 + checksum: b49e409431bf129dd89238d64299ba80717b57ff5a6d1c1a8b1a28b590d998a34e083fa13573bc732bb8d2305becb4c9a4407f8486c81fa7d55100eb08263cf8 languageName: node linkType: hard @@ -6528,13 +5903,6 @@ __metadata: languageName: node linkType: hard -"promise-inflight@npm:^1.0.1": - version: 1.0.1 - resolution: "promise-inflight@npm:1.0.1" - checksum: 22749483091d2c594261517f4f80e05226d4d5ecc1fc917e1886929da56e22b5718b7f2a75f3807e7a7d471bc3be2907fe92e6e8f373ddf5c64bae35b5af3981 - languageName: node - linkType: hard - "promise-retry@npm:^2.0.1": version: 2.0.1 resolution: "promise-retry@npm:2.0.1" @@ -6560,11 +5928,11 @@ __metadata: linkType: hard "qs@npm:^6.11.0": - version: 6.11.1 - resolution: "qs@npm:6.11.1" + version: 6.11.2 + resolution: "qs@npm:6.11.2" dependencies: side-channel: ^1.0.4 - checksum: 82ee78ef12a16f3372fae5b64f76f8aedecb000feea882bbff1af146c147f6eb66b08f9c3f34d7e076f28563586956318b9b2ca41141846cdd6d5ad6f241d52f + checksum: e812f3c590b2262548647d62f1637b6989cc56656dc960b893fe2098d96e1bd633f36576f4cd7564dfbff9db42e17775884db96d846bebe4f37420d073ecdc0b languageName: node linkType: hard @@ -6692,7 +6060,7 @@ __metadata: languageName: node linkType: hard -"resolve@npm:^1.10.0": +"resolve@npm:^1.10.0, resolve@npm:^1.20.0": version: 1.22.3 resolution: "resolve@npm:1.22.3" dependencies: @@ -6705,20 +6073,7 @@ __metadata: languageName: node linkType: hard -"resolve@npm:^1.20.0": - version: 1.22.1 - resolution: "resolve@npm:1.22.1" - dependencies: - is-core-module: ^2.9.0 - path-parse: ^1.0.7 - supports-preserve-symlinks-flag: ^1.0.0 - bin: - resolve: bin/resolve - checksum: 07af5fc1e81aa1d866cbc9e9460fbb67318a10fa3c4deadc35c3ad8a898ee9a71a86a65e4755ac3195e0ea0cfbe201eb323ebe655ce90526fd61917313a34e4e - languageName: node - linkType: hard - -"resolve@patch:resolve@^1.10.0#~builtin": +"resolve@patch:resolve@^1.10.0#~builtin, resolve@patch:resolve@^1.20.0#~builtin": version: 1.22.3 resolution: "resolve@patch:resolve@npm%3A1.22.3#~builtin::version=1.22.3&hash=07638b" dependencies: @@ -6731,19 +6086,6 @@ __metadata: languageName: node linkType: hard -"resolve@patch:resolve@^1.20.0#~builtin": - version: 1.22.1 - resolution: "resolve@patch:resolve@npm%3A1.22.1#~builtin::version=1.22.1&hash=07638b" - dependencies: - is-core-module: ^2.9.0 - path-parse: ^1.0.7 - supports-preserve-symlinks-flag: ^1.0.0 - bin: - resolve: bin/resolve - checksum: 5656f4d0bedcf8eb52685c1abdf8fbe73a1603bb1160a24d716e27a57f6cecbe2432ff9c89c2bd57542c3a7b9d14b1882b73bfe2e9d7849c9a4c0b8b39f02b8b - languageName: node - linkType: hard - "restore-cursor@npm:^3.1.0": version: 3.1.0 resolution: "restore-cursor@npm:3.1.0" @@ -6796,11 +6138,11 @@ __metadata: linkType: hard "rxjs@npm:^7.5.5": - version: 7.8.0 - resolution: "rxjs@npm:7.8.0" + version: 7.8.1 + resolution: "rxjs@npm:7.8.1" dependencies: tslib: ^2.1.0 - checksum: 61b4d4fd323c1043d8d6ceb91f24183b28bcf5def4f01ca111511d5c6b66755bc5578587fe714ef5d67cf4c9f2e26f4490d4e1d8cabf9bd5967687835e9866a2 + checksum: de4b53db1063e618ec2eca0f7965d9137cabe98cf6be9272efe6c86b47c17b987383df8574861bcced18ebd590764125a901d5506082be84a8b8e364bf05f119 languageName: node linkType: hard @@ -6832,41 +6174,32 @@ __metadata: languageName: node linkType: hard -"semver@npm:2 || 3 || 4 || 5, semver@npm:^5.7.1": - version: 5.7.1 - resolution: "semver@npm:5.7.1" +"semver@npm:2 || 3 || 4 || 5": + version: 5.7.2 + resolution: "semver@npm:5.7.2" bin: - semver: ./bin/semver - checksum: 57fd0acfd0bac382ee87cd52cd0aaa5af086a7dc8d60379dfe65fea491fb2489b6016400813930ecd61fd0952dae75c115287a1b16c234b1550887117744dfaf + semver: bin/semver + checksum: fb4ab5e0dd1c22ce0c937ea390b4a822147a9c53dbd2a9a0132f12fe382902beef4fbf12cf51bb955248d8d15874ce8cd89532569756384f994309825f10b686 languageName: node linkType: hard -"semver@npm:^6.0.0, semver@npm:^6.3.0": - version: 6.3.0 - resolution: "semver@npm:6.3.0" +"semver@npm:^6.0.0, semver@npm:^6.3.0, semver@npm:^6.3.1": + version: 6.3.1 + resolution: "semver@npm:6.3.1" bin: - semver: ./bin/semver.js - checksum: 1b26ecf6db9e8292dd90df4e781d91875c0dcc1b1909e70f5d12959a23c7eebb8f01ea581c00783bbee72ceeaad9505797c381756326073850dc36ed284b21b9 + semver: bin/semver.js + checksum: ae47d06de28836adb9d3e25f22a92943477371292d9b665fb023fae278d345d508ca1958232af086d85e0155aee22e313e100971898bbb8d5d89b8b1d4054ca2 languageName: node linkType: hard -"semver@npm:^7.3.5, semver@npm:^7.3.7, semver@npm:^7.3.8": - version: 7.3.8 - resolution: "semver@npm:7.3.8" +"semver@npm:^7.3.5, semver@npm:^7.3.7, semver@npm:^7.3.8, semver@npm:^7.5.3": + version: 7.5.4 + resolution: "semver@npm:7.5.4" dependencies: lru-cache: ^6.0.0 bin: semver: bin/semver.js - checksum: ba9c7cbbf2b7884696523450a61fee1a09930d888b7a8d7579025ad93d459b2d1949ee5bbfeb188b2be5f4ac163544c5e98491ad6152df34154feebc2cc337c1 - languageName: node - linkType: hard - -"semver@npm:~7.0.0": - version: 7.0.0 - resolution: "semver@npm:7.0.0" - bin: - semver: bin/semver.js - checksum: 272c11bf8d083274ef79fe40a81c55c184dff84dd58e3c325299d0927ba48cece1f020793d138382b85f89bab5002a35a5ba59a3a68a7eebbb597eb733838778 + checksum: 12d8ad952fa353b0995bf180cdac205a4068b759a140e5d3c608317098b3575ac2f1e09182206bf2eb26120e1c0ed8fb92c48c592f6099680de56bb071423ca3 languageName: node linkType: hard @@ -6927,6 +6260,13 @@ __metadata: languageName: node linkType: hard +"signal-exit@npm:^4.0.1": + version: 4.1.0 + resolution: "signal-exit@npm:4.1.0" + checksum: 64c757b498cb8629ffa5f75485340594d2f8189e9b08700e69199069c8e3070fb3e255f7ab873c05dc0b3cec412aea7402e10a5990cb6a050bd33ba062a6c549 + languageName: node + linkType: hard + "simple-swizzle@npm:^0.2.2": version: 0.2.2 resolution: "simple-swizzle@npm:0.2.2" @@ -6936,12 +6276,12 @@ __metadata: languageName: node linkType: hard -"simple-update-notifier@npm:^1.0.7": - version: 1.1.0 - resolution: "simple-update-notifier@npm:1.1.0" +"simple-update-notifier@npm:^2.0.0": + version: 2.0.0 + resolution: "simple-update-notifier@npm:2.0.0" dependencies: - semver: ~7.0.0 - checksum: 1012e9b6c504e559a948078177b3eedbb9d7e4d15878e2bda56314d08db609ca5da485be4ac9f838759faae8057935ee0246fcdf63f1233c86bd9fecb2a5544b + semver: ^7.5.3 + checksum: 9ba00d38ce6a29682f64a46213834e4eb01634c2f52c813a9a7b8873ca49cdbb703696f3290f3b27dc067de6d9418b0b84bef22c3eb074acf352529b2d6c27fd languageName: node linkType: hard @@ -7057,13 +6397,6 @@ __metadata: languageName: node linkType: hard -"sourcemap-codec@npm:^1.4.8": - version: 1.4.8 - resolution: "sourcemap-codec@npm:1.4.8" - checksum: b57981c05611afef31605732b598ccf65124a9fcb03b833532659ac4d29ac0f7bfacbc0d6c5a28a03e84c7510e7e556d758d0bb57786e214660016fb94279316 - languageName: node - linkType: hard - "spawn-wrap@npm:^2.0.0": version: 2.0.0 resolution: "spawn-wrap@npm:2.0.0" @@ -7126,12 +6459,12 @@ __metadata: languageName: node linkType: hard -"ssri@npm:^9.0.0": - version: 9.0.1 - resolution: "ssri@npm:9.0.1" +"ssri@npm:^10.0.0": + version: 10.0.4 + resolution: "ssri@npm:10.0.4" dependencies: - minipass: ^3.1.1 - checksum: fb58f5e46b6923ae67b87ad5ef1c5ab6d427a17db0bead84570c2df3cd50b4ceb880ebdba2d60726588272890bae842a744e1ecce5bd2a2a582fccd5068309eb + minipass: ^5.0.0 + checksum: fb14da9f8a72b04eab163eb13a9dda11d5962cd2317f85457c4e0b575e9a6e0e3a6a87b5bf122c75cb36565830cd5f263fb457571bf6f1587eb5f95d095d6165 languageName: node linkType: hard @@ -7157,13 +6490,13 @@ __metadata: linkType: hard "string-argv@npm:^0.3.1": - version: 0.3.1 - resolution: "string-argv@npm:0.3.1" - checksum: efbd0289b599bee808ce80820dfe49c9635610715429c6b7cc50750f0437e3c2f697c81e5c390208c13b5d5d12d904a1546172a88579f6ee5cbaaaa4dc9ec5cf + version: 0.3.2 + resolution: "string-argv@npm:0.3.2" + checksum: 8703ad3f3db0b2641ed2adbb15cf24d3945070d9a751f9e74a924966db9f325ac755169007233e8985a39a6a292f14d4fee20482989b89b96e473c4221508a0f languageName: node linkType: hard -"string-width@npm:^1.0.2 || 2 || 3 || 4, string-width@npm:^4.1.0, string-width@npm:^4.2.0, string-width@npm:^4.2.3": +"string-width-cjs@npm:string-width@^4.2.0, string-width@npm:^1.0.2 || 2 || 3 || 4, string-width@npm:^4.1.0, string-width@npm:^4.2.0, string-width@npm:^4.2.3": version: 4.2.3 resolution: "string-width@npm:4.2.3" dependencies: @@ -7174,7 +6507,7 @@ __metadata: languageName: node linkType: hard -"string-width@npm:^5.0.0": +"string-width@npm:^5.0.0, string-width@npm:^5.0.1, string-width@npm:^5.1.2": version: 5.1.2 resolution: "string-width@npm:5.1.2" dependencies: @@ -7194,7 +6527,7 @@ __metadata: languageName: node linkType: hard -"strip-ansi@npm:^6.0.0, strip-ansi@npm:^6.0.1": +"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: @@ -7204,11 +6537,11 @@ __metadata: linkType: hard "strip-ansi@npm:^7.0.1": - version: 7.0.1 - resolution: "strip-ansi@npm:7.0.1" + version: 7.1.0 + resolution: "strip-ansi@npm:7.1.0" dependencies: ansi-regex: ^6.0.1 - checksum: 257f78fa433520e7f9897722731d78599cb3fce29ff26a20a5e12ba4957463b50a01136f37c43707f4951817a75e90820174853d6ccc240997adc5df8f966039 + checksum: 859c73fcf27869c22a4e4d8c6acfe690064659e84bef9458aa6d13719d09ca88dcfd40cbf31fd0be63518ea1a643fe070b4827d353e09533a5b0b9fd4553d64d languageName: node linkType: hard @@ -7226,7 +6559,7 @@ __metadata: languageName: node linkType: hard -"strip-json-comments@npm:3.1.1, strip-json-comments@npm:^3.1.0, strip-json-comments@npm:^3.1.1": +"strip-json-comments@npm:3.1.1, strip-json-comments@npm:^3.1.1": version: 3.1.1 resolution: "strip-json-comments@npm:3.1.1" checksum: 492f73e27268f9b1c122733f28ecb0e7e8d8a531a6662efbd08e22cccb3f9475e90a1b82cab06a392f6afae6d2de636f977e231296400d0ec5304ba70f166443 @@ -7278,9 +6611,9 @@ __metadata: linkType: hard "supports-color@npm:^9.2.2": - version: 9.3.1 - resolution: "supports-color@npm:9.3.1" - checksum: 00c4d1082a7ba0ee21cba1d4e4a466642635412e40476777b530aa5110d035e99a420cd048e1fb6811f2254c0946095fbb87a1eccf1af1d1ca45ab0a4535db93 + version: 9.4.0 + resolution: "supports-color@npm:9.4.0" + checksum: cb8ff8daeaf1db642156f69a9aa545b6c01dd9c4def4f90a49f46cbf24be0c245d392fcf37acd119cd1819b99dad2cc9b7e3260813f64bcfd7f5b18b5a1eefb8 languageName: node linkType: hard @@ -7292,16 +6625,16 @@ __metadata: linkType: hard "tar@npm:^6.1.11, tar@npm:^6.1.2": - version: 6.1.13 - resolution: "tar@npm:6.1.13" + version: 6.1.15 + resolution: "tar@npm:6.1.15" dependencies: chownr: ^2.0.0 fs-minipass: ^2.0.0 - minipass: ^4.0.0 + minipass: ^5.0.0 minizlib: ^2.1.1 mkdirp: ^1.0.3 yallist: ^4.0.0 - checksum: 8a278bed123aa9f53549b256a36b719e317c8b96fe86a63406f3c62887f78267cea9b22dc6f7007009738509800d4a4dccc444abd71d762287c90f35b002eb1c + checksum: f23832fceeba7578bf31907aac744ae21e74a66f4a17a9e94507acf460e48f6db598c7023882db33bab75b80e027c21f276d405e4a0322d58f51c7088d428268 languageName: node linkType: hard @@ -7395,9 +6728,9 @@ __metadata: linkType: hard "triple-beam@npm:^1.3.0": - version: 1.3.0 - resolution: "triple-beam@npm:1.3.0" - checksum: 7d7b77d8625fb252c126c24984a68de462b538a8fcd1de2abd0a26421629cf3527d48e23b3c2264f08f4a6c3bc40a478a722176f4d7b6a1acc154cb70c359f2b + version: 1.4.1 + resolution: "triple-beam@npm:1.4.1" + checksum: 2e881a3e8e076b6f2b85b9ec9dd4a900d3f5016e6d21183ed98e78f9abcc0149e7d54d79a3f432b23afde46b0885bdcdcbff789f39bc75de796316961ec07f61 languageName: node linkType: hard @@ -7447,9 +6780,9 @@ __metadata: linkType: hard "tslib@npm:^2.1.0, tslib@npm:^2.3.1, tslib@npm:^2.5.0": - version: 2.5.0 - resolution: "tslib@npm:2.5.0" - checksum: ae3ed5f9ce29932d049908ebfdf21b3a003a85653a9a140d614da6b767a93ef94f460e52c3d787f0e4f383546981713f165037dc2274df212ea9f8a4541004e1 + version: 2.6.1 + resolution: "tslib@npm:2.6.1" + checksum: b0d176d176487905b66ae4d5856647df50e37beea7571c53b8d10ba9222c074b81f1410fb91da13debaf2cbc970663609068bdebafa844ea9d69b146527c38fe languageName: node linkType: hard @@ -7577,21 +6910,21 @@ __metadata: languageName: node linkType: hard -"unique-filename@npm:^2.0.0": - version: 2.0.1 - resolution: "unique-filename@npm:2.0.1" +"unique-filename@npm:^3.0.0": + version: 3.0.0 + resolution: "unique-filename@npm:3.0.0" dependencies: - unique-slug: ^3.0.0 - checksum: 807acf3381aff319086b64dc7125a9a37c09c44af7620bd4f7f3247fcd5565660ac12d8b80534dcbfd067e6fe88a67e621386dd796a8af828d1337a8420a255f + unique-slug: ^4.0.0 + checksum: 8e2f59b356cb2e54aab14ff98a51ac6c45781d15ceaab6d4f1c2228b780193dc70fae4463ce9e1df4479cb9d3304d7c2043a3fb905bdeca71cc7e8ce27e063df languageName: node linkType: hard -"unique-slug@npm:^3.0.0": - version: 3.0.0 - resolution: "unique-slug@npm:3.0.0" +"unique-slug@npm:^4.0.0": + version: 4.0.0 + resolution: "unique-slug@npm:4.0.0" dependencies: imurmurhash: ^0.1.4 - checksum: 49f8d915ba7f0101801b922062ee46b7953256c93ceca74303bd8e6413ae10aa7e8216556b54dc5382895e8221d04f1efaf75f945c2e4a515b4139f77aa6640c + checksum: 0884b58365af59f89739e6f71e3feacb5b1b41f2df2d842d0757933620e6de08eff347d27e9d499b43c40476cbaf7988638d3acb2ffbcb9d35fd035591adfd15 languageName: node linkType: hard @@ -7602,17 +6935,17 @@ __metadata: languageName: node linkType: hard -"update-browserslist-db@npm:^1.0.10": - version: 1.0.10 - resolution: "update-browserslist-db@npm:1.0.10" +"update-browserslist-db@npm:^1.0.11": + version: 1.0.11 + resolution: "update-browserslist-db@npm:1.0.11" dependencies: escalade: ^3.1.1 picocolors: ^1.0.0 peerDependencies: browserslist: ">= 4.21.0" bin: - browserslist-lint: cli.js - checksum: 12db73b4f63029ac407b153732e7cd69a1ea8206c9100b482b7d12859cd3cd0bc59c602d7ae31e652706189f1acb90d42c53ab24a5ba563ed13aebdddc5561a0 + update-browserslist-db: cli.js + checksum: b98327518f9a345c7cad5437afae4d2ae7d865f9779554baf2a200fdf4bac4969076b679b1115434bd6557376bdd37ca7583d0f9b8f8e302d7d4cc1e91b5f231 languageName: node linkType: hard @@ -7658,6 +6991,13 @@ __metadata: languageName: node linkType: hard +"validator@npm:^13.11.0": + version: 13.11.0 + resolution: "validator@npm:13.11.0" + checksum: d1e0c27022681420756da25bc03eb08d5f0c66fb008f8ff02ebc95812b77c6be6e03d3bd05cf80ca702e23eeb73dadd66b4b3683173ea2a0bc7cc72820bee131 + languageName: node + linkType: hard + "vary@npm:^1.1.2": version: 1.1.2 resolution: "vary@npm:1.1.2" @@ -7666,9 +7006,9 @@ __metadata: linkType: hard "which-module@npm:^2.0.0": - version: 2.0.0 - resolution: "which-module@npm:2.0.0" - checksum: 809f7fd3dfcb2cdbe0180b60d68100c88785084f8f9492b0998c051d7a8efe56784492609d3f09ac161635b78ea29219eb1418a98c15ce87d085bce905705c9c + version: 2.0.1 + resolution: "which-module@npm:2.0.1" + checksum: 1967b7ce17a2485544a4fdd9063599f0f773959cca24176dbe8f405e55472d748b7c549cd7920ff6abb8f1ab7db0b0f1b36de1a21c57a8ff741f4f1e792c52be languageName: node linkType: hard @@ -7704,8 +7044,8 @@ __metadata: linkType: hard "winston@npm:^3.8.2": - version: 3.8.2 - resolution: "winston@npm:3.8.2" + version: 3.10.0 + resolution: "winston@npm:3.10.0" dependencies: "@colors/colors": 1.5.0 "@dabh/diagnostics": ^2.0.2 @@ -7718,14 +7058,7 @@ __metadata: stack-trace: 0.0.x triple-beam: ^1.3.0 winston-transport: ^4.5.0 - checksum: f7b901798b92ab9e93c850110bf6e98500e9a0e762b62dab410cf928b2a4145533dfa6d3d2b24f7bf0dc94b53808d5bd28aaaeff9a4b43b89ea4c798cce308ea - languageName: node - linkType: hard - -"word-wrap@npm:^1.2.3": - version: 1.2.3 - resolution: "word-wrap@npm:1.2.3" - checksum: 30b48f91fcf12106ed3186ae4fa86a6a1842416df425be7b60485de14bec665a54a68e4b5156647dec3a70f25e84d270ca8bc8cd23182ed095f5c7206a938c1f + checksum: 47df0361220d12b46d1b3c98a1c380a3718321739d527a182ce7984fc20715e5b0b55db0bcd3fd076d1b1d3261903b890b053851cfd4bc028bda7951fa8ca2e0 languageName: node linkType: hard @@ -7743,6 +7076,17 @@ __metadata: 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: ^4.0.0 + string-width: ^4.1.0 + strip-ansi: ^6.0.0 + checksum: a790b846fd4505de962ba728a21aaeda189b8ee1c7568ca5e817d85930e06ef8d1689d49dbf0e881e8ef84436af3a88bc49115c2e2788d841ff1b8b5b51a608b + languageName: node + linkType: hard + "wrap-ansi@npm:^6.2.0": version: 6.2.0 resolution: "wrap-ansi@npm:6.2.0" @@ -7754,14 +7098,14 @@ __metadata: languageName: node linkType: hard -"wrap-ansi@npm:^7.0.0": - version: 7.0.0 - resolution: "wrap-ansi@npm:7.0.0" +"wrap-ansi@npm:^8.1.0": + version: 8.1.0 + resolution: "wrap-ansi@npm:8.1.0" dependencies: - ansi-styles: ^4.0.0 - string-width: ^4.1.0 - strip-ansi: ^6.0.0 - checksum: a790b846fd4505de962ba728a21aaeda189b8ee1c7568ca5e817d85930e06ef8d1689d49dbf0e881e8ef84436af3a88bc49115c2e2788d841ff1b8b5b51a608b + ansi-styles: ^6.1.0 + string-width: ^5.0.1 + strip-ansi: ^7.0.1 + checksum: 371733296dc2d616900ce15a0049dca0ef67597d6394c57347ba334393599e800bab03c41d4d45221b6bc967b8c453ec3ae4749eff3894202d16800fdfe0e238 languageName: node linkType: hard @@ -7827,9 +7171,9 @@ __metadata: linkType: hard "yaml@npm:^2.2.2": - version: 2.2.2 - resolution: "yaml@npm:2.2.2" - checksum: d90c235e099e30094dcff61ba3350437aef53325db4a6bcd04ca96e1bfe7e348b191f6a7a52b5211e2dbc4eeedb22a00b291527da030de7c189728ef3f2b4eb3 + version: 2.3.1 + resolution: "yaml@npm:2.3.1" + checksum: 2c7bc9a7cd4c9f40d3b0b0a98e370781b68b8b7c4515720869aced2b00d92f5da1762b4ffa947f9e795d6cd6b19f410bd4d15fdd38aca7bd96df59bd9486fb54 languageName: node linkType: hard