From a1ad8c386efce100ec531015bca6b7032e693628 Mon Sep 17 00:00:00 2001 From: Leandro Reis Date: Thu, 30 May 2024 11:24:35 -0300 Subject: [PATCH 01/43] chore: init project with typescript and jest --- .editorconfig | 2 +- .gitignore | 1 + jest.config.js | 8 + package.json | 22 + pull-request.txt | 6 +- src/index.ts | 4 + src/module/calculator.ts | 5 + test/module/calculator.test.ts | 8 + tsconfig.json | 113 ++ yarn.lock | 2284 ++++++++++++++++++++++++++++++++ 10 files changed, 2449 insertions(+), 4 deletions(-) create mode 100644 .gitignore create mode 100644 jest.config.js create mode 100644 package.json create mode 100644 src/index.ts create mode 100644 src/module/calculator.ts create mode 100644 test/module/calculator.test.ts create mode 100644 tsconfig.json create mode 100644 yarn.lock diff --git a/.editorconfig b/.editorconfig index 65a74a7a4..be859fe4c 100644 --- a/.editorconfig +++ b/.editorconfig @@ -9,5 +9,5 @@ end_of_line = lf insert_final_newline = false charset = utf-8 indent_style = space -indent_size = 4 +indent_size = 2 trim_trailing_whitespace = true diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..3c3629e64 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +node_modules diff --git a/jest.config.js b/jest.config.js new file mode 100644 index 000000000..2ab4265c9 --- /dev/null +++ b/jest.config.js @@ -0,0 +1,8 @@ +/** @type {import('ts-jest').JestConfigWithTsJest} */ +module.exports = { + preset: "ts-jest", + testEnvironment: "node", + moduleNameMapper: { + "^@/(.*)$": "/src/$1", + }, +}; diff --git a/package.json b/package.json new file mode 100644 index 000000000..555a288b4 --- /dev/null +++ b/package.json @@ -0,0 +1,22 @@ +{ + "name": "challenge-bravo", + "version": "1.0.0", + "main": "src/index.js", + "repository": "https://github.com/hurbcom/challenge-bravo.git", + "author": "Leandro Reis ", + "license": "MIT", + "scripts": { + "dev": "ts-node -r tsconfig-paths/register src/index.ts", + "test": "jest", + "test:watch": "jest --watch" + }, + "dependencies": {}, + "devDependencies": { + "@types/jest": "^29.5.12", + "jest": "^29.7.0", + "ts-jest": "^29.1.4", + "ts-node": "^10.9.2", + "tsconfig-paths": "^4.2.0", + "typescript": "^5.4.5" + } +} diff --git a/pull-request.txt b/pull-request.txt index 4eae37418..e4719f04e 100644 --- a/pull-request.txt +++ b/pull-request.txt @@ -1,3 +1,3 @@ -Your name: ___ -Your Github homepage: ___ -Original challenge URL: http://github.com/hurbcom/challenge-___ +Your name: Leandro Reis +Your Github homepage: https://github.com/leandroepr +Original challenge URL: https://github.com/hurbcom/challenge-bravo diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 000000000..cd4da232d --- /dev/null +++ b/src/index.ts @@ -0,0 +1,4 @@ +import Calculator from "@/module/calculator"; + +const calculator = new Calculator() +console.log("40 + 2 =", calculator.add(40, 2)); \ No newline at end of file diff --git a/src/module/calculator.ts b/src/module/calculator.ts new file mode 100644 index 000000000..fa614acf0 --- /dev/null +++ b/src/module/calculator.ts @@ -0,0 +1,5 @@ +export default class Calculator { + add(a: number, b: number): number { + return a + b; + } +} \ No newline at end of file diff --git a/test/module/calculator.test.ts b/test/module/calculator.test.ts new file mode 100644 index 000000000..5e4d67321 --- /dev/null +++ b/test/module/calculator.test.ts @@ -0,0 +1,8 @@ +import Calculator from "@/module/calculator"; + +describe('Calculator', () => { + it('should add two numbers', () => { + const calculator = new Calculator(); + expect(calculator.add(40, 2)).toBe(42); + }); +}); \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 000000000..414fb84ea --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,113 @@ +{ + "compilerOptions": { + /* Visit https://aka.ms/tsconfig to read more about this file */ + + /* Projects */ + "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */ + // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ + // "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */ + // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */ + // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ + // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ + + /* Language and Environment */ + "target": "ES2022", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ + "lib": ["ES2023"], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ + // "jsx": "preserve", /* Specify what JSX code is generated. */ + // "experimentalDecorators": true, /* Enable experimental support for legacy experimental decorators. */ + // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ + // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */ + // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ + // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */ + // "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */ + // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ + // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ + // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */ + + /* Modules */ + "module": "node16", /* Specify what module code is generated. */ + // "rootDir": "./", /* Specify the root folder within your source files. */ + // "moduleResolution": "node10", /* Specify how TypeScript looks up a file from a given module specifier. */ + "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ + "paths": { + "@/*": ["src/*"], + }, /* Specify a set of entries that re-map imports to additional lookup locations. */ + // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ + // "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */ + // "types": [], /* Specify type package names to be included without being referenced in a source file. */ + // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ + // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */ + // "allowImportingTsExtensions": true, /* Allow imports to include TypeScript file extensions. Requires '--moduleResolution bundler' and either '--noEmit' or '--emitDeclarationOnly' to be set. */ + // "resolvePackageJsonExports": true, /* Use the package.json 'exports' field when resolving package imports. */ + // "resolvePackageJsonImports": true, /* Use the package.json 'imports' field when resolving imports. */ + // "customConditions": [], /* Conditions to set in addition to the resolver-specific defaults when resolving imports. */ + // "resolveJsonModule": true, /* Enable importing .json files. */ + // "allowArbitraryExtensions": true, /* Enable importing files with any extension, provided a declaration file is present. */ + // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */ + + /* JavaScript Support */ + // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */ + // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ + // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */ + + /* Emit */ + // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ + // "declarationMap": true, /* Create sourcemaps for d.ts files. */ + // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ + // "sourceMap": true, /* Create source map files for emitted JavaScript files. */ + // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ + // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */ + "outDir": "./dist", /* Specify an output folder for all emitted files. */ + // "removeComments": true, /* Disable emitting comments. */ + // "noEmit": true, /* Disable emitting files from a compilation. */ + // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ + // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */ + // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ + // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ + // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ + // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ + // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ + // "newLine": "crlf", /* Set the newline character for emitting files. */ + // "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */ + // "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */ + // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ + // "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */ + // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ + // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */ + + /* Interop Constraints */ + // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ + // "verbatimModuleSyntax": true, /* Do not transform or elide any imports or exports not marked as type-only, ensuring they are written in the output file's format based on the 'module' setting. */ + // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ + "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */ + // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ + "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */ + + /* Type Checking */ + "strict": true, /* Enable all strict type-checking options. */ + // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */ + // "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */ + // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ + // "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */ + // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ + // "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */ + // "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */ + // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ + // "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */ + // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */ + // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ + // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ + // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ + // "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */ + // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ + // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */ + // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ + // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ + + /* Completeness */ + // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ + "skipLibCheck": true /* Skip type checking all .d.ts files. */ + }, + "include": ["src/**/*", "test/**/*"], + "exclude": ["node_modules", "dist"], +} diff --git a/yarn.lock b/yarn.lock new file mode 100644 index 000000000..000379e65 --- /dev/null +++ b/yarn.lock @@ -0,0 +1,2284 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@ampproject/remapping@^2.2.0": + version "2.3.0" + resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.3.0.tgz#ed441b6fa600072520ce18b43d2c8cc8caecc7f4" + integrity sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw== + dependencies: + "@jridgewell/gen-mapping" "^0.3.5" + "@jridgewell/trace-mapping" "^0.3.24" + +"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.12.13", "@babel/code-frame@^7.24.6": + version "7.24.6" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.24.6.tgz#ab88da19344445c3d8889af2216606d3329f3ef2" + integrity sha512-ZJhac6FkEd1yhG2AHOmfcXG4ceoLltoCVJjN5XsWN9BifBQr+cHJbWi0h68HZuSORq+3WtJ2z0hwF2NG1b5kcA== + dependencies: + "@babel/highlight" "^7.24.6" + picocolors "^1.0.0" + +"@babel/compat-data@^7.24.6": + version "7.24.6" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.24.6.tgz#b3600217688cabb26e25f8e467019e66d71b7ae2" + integrity sha512-aC2DGhBq5eEdyXWqrDInSqQjO0k8xtPRf5YylULqx8MCd6jBtzqfta/3ETMRpuKIc5hyswfO80ObyA1MvkCcUQ== + +"@babel/core@^7.11.6", "@babel/core@^7.12.3", "@babel/core@^7.23.9": + version "7.24.6" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.24.6.tgz#8650e0e4b03589ebe886c4e4a60398db0a7ec787" + integrity sha512-qAHSfAdVyFmIvl0VHELib8xar7ONuSHrE2hLnsaWkYNTI68dmi1x8GYDhJjMI/e7XWal9QBlZkwbOnkcw7Z8gQ== + dependencies: + "@ampproject/remapping" "^2.2.0" + "@babel/code-frame" "^7.24.6" + "@babel/generator" "^7.24.6" + "@babel/helper-compilation-targets" "^7.24.6" + "@babel/helper-module-transforms" "^7.24.6" + "@babel/helpers" "^7.24.6" + "@babel/parser" "^7.24.6" + "@babel/template" "^7.24.6" + "@babel/traverse" "^7.24.6" + "@babel/types" "^7.24.6" + convert-source-map "^2.0.0" + debug "^4.1.0" + gensync "^1.0.0-beta.2" + json5 "^2.2.3" + semver "^6.3.1" + +"@babel/generator@^7.24.6", "@babel/generator@^7.7.2": + version "7.24.6" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.24.6.tgz#dfac82a228582a9d30c959fe50ad28951d4737a7" + integrity sha512-S7m4eNa6YAPJRHmKsLHIDJhNAGNKoWNiWefz1MBbpnt8g9lvMDl1hir4P9bo/57bQEmuwEhnRU/AMWsD0G/Fbg== + dependencies: + "@babel/types" "^7.24.6" + "@jridgewell/gen-mapping" "^0.3.5" + "@jridgewell/trace-mapping" "^0.3.25" + jsesc "^2.5.1" + +"@babel/helper-compilation-targets@^7.24.6": + version "7.24.6" + resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.24.6.tgz#4a51d681f7680043d38e212715e2a7b1ad29cb51" + integrity sha512-VZQ57UsDGlX/5fFA7GkVPplZhHsVc+vuErWgdOiysI9Ksnw0Pbbd6pnPiR/mmJyKHgyIW0c7KT32gmhiF+cirg== + dependencies: + "@babel/compat-data" "^7.24.6" + "@babel/helper-validator-option" "^7.24.6" + browserslist "^4.22.2" + lru-cache "^5.1.1" + semver "^6.3.1" + +"@babel/helper-environment-visitor@^7.24.6": + version "7.24.6" + resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.24.6.tgz#ac7ad5517821641550f6698dd5468f8cef78620d" + integrity sha512-Y50Cg3k0LKLMjxdPjIl40SdJgMB85iXn27Vk/qbHZCFx/o5XO3PSnpi675h1KEmmDb6OFArfd5SCQEQ5Q4H88g== + +"@babel/helper-function-name@^7.24.6": + version "7.24.6" + resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.24.6.tgz#cebdd063386fdb95d511d84b117e51fc68fec0c8" + integrity sha512-xpeLqeeRkbxhnYimfr2PC+iA0Q7ljX/d1eZ9/inYbmfG2jpl8Lu3DyXvpOAnrS5kxkfOWJjioIMQsaMBXFI05w== + dependencies: + "@babel/template" "^7.24.6" + "@babel/types" "^7.24.6" + +"@babel/helper-hoist-variables@^7.24.6": + version "7.24.6" + resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.24.6.tgz#8a7ece8c26756826b6ffcdd0e3cf65de275af7f9" + integrity sha512-SF/EMrC3OD7dSta1bLJIlrsVxwtd0UpjRJqLno6125epQMJ/kyFmpTT4pbvPbdQHzCHg+biQ7Syo8lnDtbR+uA== + dependencies: + "@babel/types" "^7.24.6" + +"@babel/helper-module-imports@^7.24.6": + version "7.24.6" + resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.24.6.tgz#65e54ffceed6a268dc4ce11f0433b82cfff57852" + integrity sha512-a26dmxFJBF62rRO9mmpgrfTLsAuyHk4e1hKTUkD/fcMfynt8gvEKwQPQDVxWhca8dHoDck+55DFt42zV0QMw5g== + dependencies: + "@babel/types" "^7.24.6" + +"@babel/helper-module-transforms@^7.24.6": + version "7.24.6" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.24.6.tgz#22346ed9df44ce84dee850d7433c5b73fab1fe4e" + integrity sha512-Y/YMPm83mV2HJTbX1Qh2sjgjqcacvOlhbzdCCsSlblOKjSYmQqEbO6rUniWQyRo9ncyfjT8hnUjlG06RXDEmcA== + dependencies: + "@babel/helper-environment-visitor" "^7.24.6" + "@babel/helper-module-imports" "^7.24.6" + "@babel/helper-simple-access" "^7.24.6" + "@babel/helper-split-export-declaration" "^7.24.6" + "@babel/helper-validator-identifier" "^7.24.6" + +"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.14.5", "@babel/helper-plugin-utils@^7.24.6", "@babel/helper-plugin-utils@^7.8.0": + version "7.24.6" + resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.6.tgz#fa02a32410a15a6e8f8185bcbf608f10528d2a24" + integrity sha512-MZG/JcWfxybKwsA9N9PmtF2lOSFSEMVCpIRrbxccZFLJPrJciJdG/UhSh5W96GEteJI2ARqm5UAHxISwRDLSNg== + +"@babel/helper-simple-access@^7.24.6": + version "7.24.6" + resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.24.6.tgz#1d6e04d468bba4fc963b4906f6dac6286cfedff1" + integrity sha512-nZzcMMD4ZhmB35MOOzQuiGO5RzL6tJbsT37Zx8M5L/i9KSrukGXWTjLe1knIbb/RmxoJE9GON9soq0c0VEMM5g== + dependencies: + "@babel/types" "^7.24.6" + +"@babel/helper-split-export-declaration@^7.24.6": + version "7.24.6" + resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.24.6.tgz#e830068f7ba8861c53b7421c284da30ae656d7a3" + integrity sha512-CvLSkwXGWnYlF9+J3iZUvwgAxKiYzK3BWuo+mLzD/MDGOZDj7Gq8+hqaOkMxmJwmlv0iu86uH5fdADd9Hxkymw== + dependencies: + "@babel/types" "^7.24.6" + +"@babel/helper-string-parser@^7.24.6": + version "7.24.6" + resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.24.6.tgz#28583c28b15f2a3339cfafafeaad42f9a0e828df" + integrity sha512-WdJjwMEkmBicq5T9fm/cHND3+UlFa2Yj8ALLgmoSQAJZysYbBjw+azChSGPN4DSPLXOcooGRvDwZWMcF/mLO2Q== + +"@babel/helper-validator-identifier@^7.24.6": + version "7.24.6" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.6.tgz#08bb6612b11bdec78f3feed3db196da682454a5e" + integrity sha512-4yA7s865JHaqUdRbnaxarZREuPTHrjpDT+pXoAZ1yhyo6uFnIEpS8VMu16siFOHDpZNKYv5BObhsB//ycbICyw== + +"@babel/helper-validator-option@^7.24.6": + version "7.24.6" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.24.6.tgz#59d8e81c40b7d9109ab7e74457393442177f460a" + integrity sha512-Jktc8KkF3zIkePb48QO+IapbXlSapOW9S+ogZZkcO6bABgYAxtZcjZ/O005111YLf+j4M84uEgwYoidDkXbCkQ== + +"@babel/helpers@^7.24.6": + version "7.24.6" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.24.6.tgz#cd124245299e494bd4e00edda0e4ea3545c2c176" + integrity sha512-V2PI+NqnyFu1i0GyTd/O/cTpxzQCYioSkUIRmgo7gFEHKKCg5w46+r/A6WeUR1+P3TeQ49dspGPNd/E3n9AnnA== + dependencies: + "@babel/template" "^7.24.6" + "@babel/types" "^7.24.6" + +"@babel/highlight@^7.24.6": + version "7.24.6" + resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.24.6.tgz#6d610c1ebd2c6e061cade0153bf69b0590b7b3df" + integrity sha512-2YnuOp4HAk2BsBrJJvYCbItHx0zWscI1C3zgWkz+wDyD9I7GIVrfnLyrR4Y1VR+7p+chAEcrgRQYZAGIKMV7vQ== + dependencies: + "@babel/helper-validator-identifier" "^7.24.6" + chalk "^2.4.2" + js-tokens "^4.0.0" + picocolors "^1.0.0" + +"@babel/parser@^7.1.0", "@babel/parser@^7.14.7", "@babel/parser@^7.20.7", "@babel/parser@^7.23.9", "@babel/parser@^7.24.6": + version "7.24.6" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.24.6.tgz#5e030f440c3c6c78d195528c3b688b101a365328" + integrity sha512-eNZXdfU35nJC2h24RznROuOpO94h6x8sg9ju0tT9biNtLZ2vuP8SduLqqV+/8+cebSLV9SJEAN5Z3zQbJG/M+Q== + +"@babel/plugin-syntax-async-generators@^7.8.4": + version "7.8.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz#a983fb1aeb2ec3f6ed042a210f640e90e786fe0d" + integrity sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-bigint@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz#4c9a6f669f5d0cdf1b90a1671e9a146be5300cea" + integrity sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-class-properties@^7.8.3": + version "7.12.13" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz#b5c987274c4a3a82b89714796931a6b53544ae10" + integrity sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA== + dependencies: + "@babel/helper-plugin-utils" "^7.12.13" + +"@babel/plugin-syntax-import-meta@^7.8.3": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz#ee601348c370fa334d2207be158777496521fd51" + integrity sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/plugin-syntax-json-strings@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz#01ca21b668cd8218c9e640cb6dd88c5412b2c96a" + integrity sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-jsx@^7.7.2": + version "7.24.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.24.6.tgz#bcca2964150437f88f65e3679e3d68762287b9c8" + integrity sha512-lWfvAIFNWMlCsU0DRUun2GpFwZdGTukLaHJqRh1JRb80NdAP5Sb1HDHB5X9P9OtgZHQl089UzQkpYlBq2VTPRw== + dependencies: + "@babel/helper-plugin-utils" "^7.24.6" + +"@babel/plugin-syntax-logical-assignment-operators@^7.8.3": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz#ca91ef46303530448b906652bac2e9fe9941f699" + integrity sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/plugin-syntax-nullish-coalescing-operator@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz#167ed70368886081f74b5c36c65a88c03b66d1a9" + integrity sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-numeric-separator@^7.8.3": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz#b9b070b3e33570cd9fd07ba7fa91c0dd37b9af97" + integrity sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/plugin-syntax-object-rest-spread@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz#60e225edcbd98a640332a2e72dd3e66f1af55871" + integrity sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-optional-catch-binding@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz#6111a265bcfb020eb9efd0fdfd7d26402b9ed6c1" + integrity sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-optional-chaining@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz#4f69c2ab95167e0180cd5336613f8c5788f7d48a" + integrity sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-top-level-await@^7.8.3": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz#c1cfdadc35a646240001f06138247b741c34d94c" + integrity sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw== + dependencies: + "@babel/helper-plugin-utils" "^7.14.5" + +"@babel/plugin-syntax-typescript@^7.7.2": + version "7.24.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.24.6.tgz#769daf2982d60308bc83d8936eaecb7582463c87" + integrity sha512-TzCtxGgVTEJWWwcYwQhCIQ6WaKlo80/B+Onsk4RRCcYqpYGFcG9etPW94VToGte5AAcxRrhjPUFvUS3Y2qKi4A== + dependencies: + "@babel/helper-plugin-utils" "^7.24.6" + +"@babel/template@^7.24.6", "@babel/template@^7.3.3": + version "7.24.6" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.24.6.tgz#048c347b2787a6072b24c723664c8d02b67a44f9" + integrity sha512-3vgazJlLwNXi9jhrR1ef8qiB65L1RK90+lEQwv4OxveHnqC3BfmnHdgySwRLzf6akhlOYenT+b7AfWq+a//AHw== + dependencies: + "@babel/code-frame" "^7.24.6" + "@babel/parser" "^7.24.6" + "@babel/types" "^7.24.6" + +"@babel/traverse@^7.24.6": + version "7.24.6" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.24.6.tgz#0941ec50cdeaeacad0911eb67ae227a4f8424edc" + integrity sha512-OsNjaJwT9Zn8ozxcfoBc+RaHdj3gFmCmYoQLUII1o6ZrUwku0BMg80FoOTPx+Gi6XhcQxAYE4xyjPTo4SxEQqw== + dependencies: + "@babel/code-frame" "^7.24.6" + "@babel/generator" "^7.24.6" + "@babel/helper-environment-visitor" "^7.24.6" + "@babel/helper-function-name" "^7.24.6" + "@babel/helper-hoist-variables" "^7.24.6" + "@babel/helper-split-export-declaration" "^7.24.6" + "@babel/parser" "^7.24.6" + "@babel/types" "^7.24.6" + debug "^4.3.1" + globals "^11.1.0" + +"@babel/types@^7.0.0", "@babel/types@^7.20.7", "@babel/types@^7.24.6", "@babel/types@^7.3.3": + version "7.24.6" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.24.6.tgz#ba4e1f59870c10dc2fa95a274ac4feec23b21912" + integrity sha512-WaMsgi6Q8zMgMth93GvWPXkhAIEobfsIkLTacoVZoK1J0CevIPGYY2Vo5YvJGqyHqXM6P4ppOYGsIRU8MM9pFQ== + dependencies: + "@babel/helper-string-parser" "^7.24.6" + "@babel/helper-validator-identifier" "^7.24.6" + to-fast-properties "^2.0.0" + +"@bcoe/v8-coverage@^0.2.3": + version "0.2.3" + resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" + integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw== + +"@cspotcode/source-map-support@^0.8.0": + version "0.8.1" + resolved "https://registry.yarnpkg.com/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz#00629c35a688e05a88b1cda684fb9d5e73f000a1" + integrity sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw== + dependencies: + "@jridgewell/trace-mapping" "0.3.9" + +"@istanbuljs/load-nyc-config@^1.0.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz#fd3db1d59ecf7cf121e80650bb86712f9b55eced" + integrity sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ== + dependencies: + camelcase "^5.3.1" + find-up "^4.1.0" + get-package-type "^0.1.0" + js-yaml "^3.13.1" + resolve-from "^5.0.0" + +"@istanbuljs/schema@^0.1.2", "@istanbuljs/schema@^0.1.3": + version "0.1.3" + resolved "https://registry.yarnpkg.com/@istanbuljs/schema/-/schema-0.1.3.tgz#e45e384e4b8ec16bce2fd903af78450f6bf7ec98" + integrity sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA== + +"@jest/console@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/console/-/console-29.7.0.tgz#cd4822dbdb84529265c5a2bdb529a3c9cc950ffc" + integrity sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg== + dependencies: + "@jest/types" "^29.6.3" + "@types/node" "*" + chalk "^4.0.0" + jest-message-util "^29.7.0" + jest-util "^29.7.0" + slash "^3.0.0" + +"@jest/core@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/core/-/core-29.7.0.tgz#b6cccc239f30ff36609658c5a5e2291757ce448f" + integrity sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg== + dependencies: + "@jest/console" "^29.7.0" + "@jest/reporters" "^29.7.0" + "@jest/test-result" "^29.7.0" + "@jest/transform" "^29.7.0" + "@jest/types" "^29.6.3" + "@types/node" "*" + ansi-escapes "^4.2.1" + chalk "^4.0.0" + ci-info "^3.2.0" + exit "^0.1.2" + graceful-fs "^4.2.9" + jest-changed-files "^29.7.0" + jest-config "^29.7.0" + jest-haste-map "^29.7.0" + jest-message-util "^29.7.0" + jest-regex-util "^29.6.3" + jest-resolve "^29.7.0" + jest-resolve-dependencies "^29.7.0" + jest-runner "^29.7.0" + jest-runtime "^29.7.0" + jest-snapshot "^29.7.0" + jest-util "^29.7.0" + jest-validate "^29.7.0" + jest-watcher "^29.7.0" + micromatch "^4.0.4" + pretty-format "^29.7.0" + slash "^3.0.0" + strip-ansi "^6.0.0" + +"@jest/environment@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-29.7.0.tgz#24d61f54ff1f786f3cd4073b4b94416383baf2a7" + integrity sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw== + dependencies: + "@jest/fake-timers" "^29.7.0" + "@jest/types" "^29.6.3" + "@types/node" "*" + jest-mock "^29.7.0" + +"@jest/expect-utils@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/expect-utils/-/expect-utils-29.7.0.tgz#023efe5d26a8a70f21677d0a1afc0f0a44e3a1c6" + integrity sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA== + dependencies: + jest-get-type "^29.6.3" + +"@jest/expect@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/expect/-/expect-29.7.0.tgz#76a3edb0cb753b70dfbfe23283510d3d45432bf2" + integrity sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ== + dependencies: + expect "^29.7.0" + jest-snapshot "^29.7.0" + +"@jest/fake-timers@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-29.7.0.tgz#fd91bf1fffb16d7d0d24a426ab1a47a49881a565" + integrity sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ== + dependencies: + "@jest/types" "^29.6.3" + "@sinonjs/fake-timers" "^10.0.2" + "@types/node" "*" + jest-message-util "^29.7.0" + jest-mock "^29.7.0" + jest-util "^29.7.0" + +"@jest/globals@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/globals/-/globals-29.7.0.tgz#8d9290f9ec47ff772607fa864ca1d5a2efae1d4d" + integrity sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ== + dependencies: + "@jest/environment" "^29.7.0" + "@jest/expect" "^29.7.0" + "@jest/types" "^29.6.3" + jest-mock "^29.7.0" + +"@jest/reporters@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/reporters/-/reporters-29.7.0.tgz#04b262ecb3b8faa83b0b3d321623972393e8f4c7" + integrity sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg== + dependencies: + "@bcoe/v8-coverage" "^0.2.3" + "@jest/console" "^29.7.0" + "@jest/test-result" "^29.7.0" + "@jest/transform" "^29.7.0" + "@jest/types" "^29.6.3" + "@jridgewell/trace-mapping" "^0.3.18" + "@types/node" "*" + chalk "^4.0.0" + collect-v8-coverage "^1.0.0" + exit "^0.1.2" + glob "^7.1.3" + graceful-fs "^4.2.9" + istanbul-lib-coverage "^3.0.0" + istanbul-lib-instrument "^6.0.0" + istanbul-lib-report "^3.0.0" + istanbul-lib-source-maps "^4.0.0" + istanbul-reports "^3.1.3" + jest-message-util "^29.7.0" + jest-util "^29.7.0" + jest-worker "^29.7.0" + slash "^3.0.0" + string-length "^4.0.1" + strip-ansi "^6.0.0" + v8-to-istanbul "^9.0.1" + +"@jest/schemas@^29.6.3": + version "29.6.3" + resolved "https://registry.yarnpkg.com/@jest/schemas/-/schemas-29.6.3.tgz#430b5ce8a4e0044a7e3819663305a7b3091c8e03" + integrity sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA== + dependencies: + "@sinclair/typebox" "^0.27.8" + +"@jest/source-map@^29.6.3": + version "29.6.3" + resolved "https://registry.yarnpkg.com/@jest/source-map/-/source-map-29.6.3.tgz#d90ba772095cf37a34a5eb9413f1b562a08554c4" + integrity sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw== + dependencies: + "@jridgewell/trace-mapping" "^0.3.18" + callsites "^3.0.0" + graceful-fs "^4.2.9" + +"@jest/test-result@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/test-result/-/test-result-29.7.0.tgz#8db9a80aa1a097bb2262572686734baed9b1657c" + integrity sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA== + dependencies: + "@jest/console" "^29.7.0" + "@jest/types" "^29.6.3" + "@types/istanbul-lib-coverage" "^2.0.0" + collect-v8-coverage "^1.0.0" + +"@jest/test-sequencer@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/test-sequencer/-/test-sequencer-29.7.0.tgz#6cef977ce1d39834a3aea887a1726628a6f072ce" + integrity sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw== + dependencies: + "@jest/test-result" "^29.7.0" + graceful-fs "^4.2.9" + jest-haste-map "^29.7.0" + slash "^3.0.0" + +"@jest/transform@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/transform/-/transform-29.7.0.tgz#df2dd9c346c7d7768b8a06639994640c642e284c" + integrity sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw== + dependencies: + "@babel/core" "^7.11.6" + "@jest/types" "^29.6.3" + "@jridgewell/trace-mapping" "^0.3.18" + babel-plugin-istanbul "^6.1.1" + chalk "^4.0.0" + convert-source-map "^2.0.0" + fast-json-stable-stringify "^2.1.0" + graceful-fs "^4.2.9" + jest-haste-map "^29.7.0" + jest-regex-util "^29.6.3" + jest-util "^29.7.0" + micromatch "^4.0.4" + pirates "^4.0.4" + slash "^3.0.0" + write-file-atomic "^4.0.2" + +"@jest/types@^29.6.3": + version "29.6.3" + resolved "https://registry.yarnpkg.com/@jest/types/-/types-29.6.3.tgz#1131f8cf634e7e84c5e77bab12f052af585fba59" + integrity sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw== + dependencies: + "@jest/schemas" "^29.6.3" + "@types/istanbul-lib-coverage" "^2.0.0" + "@types/istanbul-reports" "^3.0.0" + "@types/node" "*" + "@types/yargs" "^17.0.8" + chalk "^4.0.0" + +"@jridgewell/gen-mapping@^0.3.5": + version "0.3.5" + resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz#dcce6aff74bdf6dad1a95802b69b04a2fcb1fb36" + integrity sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg== + dependencies: + "@jridgewell/set-array" "^1.2.1" + "@jridgewell/sourcemap-codec" "^1.4.10" + "@jridgewell/trace-mapping" "^0.3.24" + +"@jridgewell/resolve-uri@^3.0.3", "@jridgewell/resolve-uri@^3.1.0": + version "3.1.2" + resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz#7a0ee601f60f99a20c7c7c5ff0c80388c1189bd6" + integrity sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw== + +"@jridgewell/set-array@^1.2.1": + version "1.2.1" + resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.2.1.tgz#558fb6472ed16a4c850b889530e6b36438c49280" + integrity sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A== + +"@jridgewell/sourcemap-codec@^1.4.10", "@jridgewell/sourcemap-codec@^1.4.14": + version "1.4.15" + resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz#d7c6e6755c78567a951e04ab52ef0fd26de59f32" + integrity sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg== + +"@jridgewell/trace-mapping@0.3.9": + version "0.3.9" + resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz#6534fd5933a53ba7cbf3a17615e273a0d1273ff9" + integrity sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ== + dependencies: + "@jridgewell/resolve-uri" "^3.0.3" + "@jridgewell/sourcemap-codec" "^1.4.10" + +"@jridgewell/trace-mapping@^0.3.12", "@jridgewell/trace-mapping@^0.3.18", "@jridgewell/trace-mapping@^0.3.24", "@jridgewell/trace-mapping@^0.3.25": + version "0.3.25" + resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz#15f190e98895f3fc23276ee14bc76b675c2e50f0" + integrity sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ== + dependencies: + "@jridgewell/resolve-uri" "^3.1.0" + "@jridgewell/sourcemap-codec" "^1.4.14" + +"@sinclair/typebox@^0.27.8": + version "0.27.8" + resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.27.8.tgz#6667fac16c436b5434a387a34dedb013198f6e6e" + integrity sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA== + +"@sinonjs/commons@^3.0.0": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-3.0.1.tgz#1029357e44ca901a615585f6d27738dbc89084cd" + integrity sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ== + dependencies: + type-detect "4.0.8" + +"@sinonjs/fake-timers@^10.0.2": + version "10.3.0" + resolved "https://registry.yarnpkg.com/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz#55fdff1ecab9f354019129daf4df0dd4d923ea66" + integrity sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA== + dependencies: + "@sinonjs/commons" "^3.0.0" + +"@tsconfig/node10@^1.0.7": + version "1.0.11" + resolved "https://registry.yarnpkg.com/@tsconfig/node10/-/node10-1.0.11.tgz#6ee46400685f130e278128c7b38b7e031ff5b2f2" + integrity sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw== + +"@tsconfig/node12@^1.0.7": + version "1.0.11" + resolved "https://registry.yarnpkg.com/@tsconfig/node12/-/node12-1.0.11.tgz#ee3def1f27d9ed66dac6e46a295cffb0152e058d" + integrity sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag== + +"@tsconfig/node14@^1.0.0": + version "1.0.3" + resolved "https://registry.yarnpkg.com/@tsconfig/node14/-/node14-1.0.3.tgz#e4386316284f00b98435bf40f72f75a09dabf6c1" + integrity sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow== + +"@tsconfig/node16@^1.0.2": + version "1.0.4" + resolved "https://registry.yarnpkg.com/@tsconfig/node16/-/node16-1.0.4.tgz#0b92dcc0cc1c81f6f306a381f28e31b1a56536e9" + integrity sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA== + +"@types/babel__core@^7.1.14": + version "7.20.5" + resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.20.5.tgz#3df15f27ba85319caa07ba08d0721889bb39c017" + integrity sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA== + dependencies: + "@babel/parser" "^7.20.7" + "@babel/types" "^7.20.7" + "@types/babel__generator" "*" + "@types/babel__template" "*" + "@types/babel__traverse" "*" + +"@types/babel__generator@*": + version "7.6.8" + resolved "https://registry.yarnpkg.com/@types/babel__generator/-/babel__generator-7.6.8.tgz#f836c61f48b1346e7d2b0d93c6dacc5b9535d3ab" + integrity sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw== + dependencies: + "@babel/types" "^7.0.0" + +"@types/babel__template@*": + version "7.4.4" + resolved "https://registry.yarnpkg.com/@types/babel__template/-/babel__template-7.4.4.tgz#5672513701c1b2199bc6dad636a9d7491586766f" + integrity sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A== + dependencies: + "@babel/parser" "^7.1.0" + "@babel/types" "^7.0.0" + +"@types/babel__traverse@*", "@types/babel__traverse@^7.0.6": + version "7.20.6" + resolved "https://registry.yarnpkg.com/@types/babel__traverse/-/babel__traverse-7.20.6.tgz#8dc9f0ae0f202c08d8d4dab648912c8d6038e3f7" + integrity sha512-r1bzfrm0tomOI8g1SzvCaQHo6Lcv6zu0EA+W2kHrt8dyrHQxGzBBL4kdkzIS+jBMV+EYcMAEAqXqYaLJq5rOZg== + dependencies: + "@babel/types" "^7.20.7" + +"@types/graceful-fs@^4.1.3": + version "4.1.9" + resolved "https://registry.yarnpkg.com/@types/graceful-fs/-/graceful-fs-4.1.9.tgz#2a06bc0f68a20ab37b3e36aa238be6abdf49e8b4" + integrity sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ== + dependencies: + "@types/node" "*" + +"@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0", "@types/istanbul-lib-coverage@^2.0.1": + version "2.0.6" + resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz#7739c232a1fee9b4d3ce8985f314c0c6d33549d7" + integrity sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w== + +"@types/istanbul-lib-report@*": + version "3.0.3" + resolved "https://registry.yarnpkg.com/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz#53047614ae72e19fc0401d872de3ae2b4ce350bf" + integrity sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA== + dependencies: + "@types/istanbul-lib-coverage" "*" + +"@types/istanbul-reports@^3.0.0": + version "3.0.4" + resolved "https://registry.yarnpkg.com/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz#0f03e3d2f670fbdac586e34b433783070cc16f54" + integrity sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ== + dependencies: + "@types/istanbul-lib-report" "*" + +"@types/jest@^29.5.12": + version "29.5.12" + resolved "https://registry.yarnpkg.com/@types/jest/-/jest-29.5.12.tgz#7f7dc6eb4cf246d2474ed78744b05d06ce025544" + integrity sha512-eDC8bTvT/QhYdxJAulQikueigY5AsdBRH2yDKW3yveW7svY3+DzN84/2NUgkw10RTiJbWqZrTtoGVdYlvFJdLw== + dependencies: + expect "^29.0.0" + pretty-format "^29.0.0" + +"@types/node@*": + version "20.12.13" + resolved "https://registry.yarnpkg.com/@types/node/-/node-20.12.13.tgz#90ed3b8a4e52dd3c5dc5a42dde5b85b74ad8ed88" + integrity sha512-gBGeanV41c1L171rR7wjbMiEpEI/l5XFQdLLfhr/REwpgDy/4U8y89+i8kRiLzDyZdOkXh+cRaTetUnCYutoXA== + dependencies: + undici-types "~5.26.4" + +"@types/stack-utils@^2.0.0": + version "2.0.3" + resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.3.tgz#6209321eb2c1712a7e7466422b8cb1fc0d9dd5d8" + integrity sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw== + +"@types/yargs-parser@*": + version "21.0.3" + resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-21.0.3.tgz#815e30b786d2e8f0dcd85fd5bcf5e1a04d008f15" + integrity sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ== + +"@types/yargs@^17.0.8": + version "17.0.32" + resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-17.0.32.tgz#030774723a2f7faafebf645f4e5a48371dca6229" + integrity sha512-xQ67Yc/laOG5uMfX/093MRlGGCIBzZMarVa+gfNKJxWAIgykYpVGkBdbqEzGDDfCrVUj6Hiff4mTZ5BA6TmAog== + dependencies: + "@types/yargs-parser" "*" + +acorn-walk@^8.1.1: + version "8.3.2" + resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.3.2.tgz#7703af9415f1b6db9315d6895503862e231d34aa" + integrity sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A== + +acorn@^8.4.1: + version "8.11.3" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.11.3.tgz#71e0b14e13a4ec160724b38fb7b0f233b1b81d7a" + integrity sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg== + +ansi-escapes@^4.2.1: + version "4.3.2" + resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.3.2.tgz#6b2291d1db7d98b6521d5f1efa42d0f3a9feb65e" + integrity sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ== + dependencies: + type-fest "^0.21.3" + +ansi-regex@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" + integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== + +ansi-styles@^3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" + integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== + dependencies: + color-convert "^1.9.0" + +ansi-styles@^4.0.0, ansi-styles@^4.1.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" + integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== + dependencies: + color-convert "^2.0.1" + +ansi-styles@^5.0.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-5.2.0.tgz#07449690ad45777d1924ac2abb2fc8895dba836b" + integrity sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA== + +anymatch@^3.0.3: + version "3.1.3" + resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.3.tgz#790c58b19ba1720a84205b57c618d5ad8524973e" + integrity sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw== + dependencies: + normalize-path "^3.0.0" + picomatch "^2.0.4" + +arg@^4.1.0: + version "4.1.3" + resolved "https://registry.yarnpkg.com/arg/-/arg-4.1.3.tgz#269fc7ad5b8e42cb63c896d5666017261c144089" + integrity sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA== + +argparse@^1.0.7: + version "1.0.10" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" + integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg== + dependencies: + sprintf-js "~1.0.2" + +babel-jest@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-29.7.0.tgz#f4369919225b684c56085998ac63dbd05be020d5" + integrity sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg== + dependencies: + "@jest/transform" "^29.7.0" + "@types/babel__core" "^7.1.14" + babel-plugin-istanbul "^6.1.1" + babel-preset-jest "^29.6.3" + chalk "^4.0.0" + graceful-fs "^4.2.9" + slash "^3.0.0" + +babel-plugin-istanbul@^6.1.1: + version "6.1.1" + resolved "https://registry.yarnpkg.com/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz#fa88ec59232fd9b4e36dbbc540a8ec9a9b47da73" + integrity sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + "@istanbuljs/load-nyc-config" "^1.0.0" + "@istanbuljs/schema" "^0.1.2" + istanbul-lib-instrument "^5.0.4" + test-exclude "^6.0.0" + +babel-plugin-jest-hoist@^29.6.3: + version "29.6.3" + resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.6.3.tgz#aadbe943464182a8922c3c927c3067ff40d24626" + integrity sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg== + dependencies: + "@babel/template" "^7.3.3" + "@babel/types" "^7.3.3" + "@types/babel__core" "^7.1.14" + "@types/babel__traverse" "^7.0.6" + +babel-preset-current-node-syntax@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.1.tgz#b4399239b89b2a011f9ddbe3e4f401fc40cff73b" + integrity sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ== + dependencies: + "@babel/plugin-syntax-async-generators" "^7.8.4" + "@babel/plugin-syntax-bigint" "^7.8.3" + "@babel/plugin-syntax-class-properties" "^7.8.3" + "@babel/plugin-syntax-import-meta" "^7.8.3" + "@babel/plugin-syntax-json-strings" "^7.8.3" + "@babel/plugin-syntax-logical-assignment-operators" "^7.8.3" + "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3" + "@babel/plugin-syntax-numeric-separator" "^7.8.3" + "@babel/plugin-syntax-object-rest-spread" "^7.8.3" + "@babel/plugin-syntax-optional-catch-binding" "^7.8.3" + "@babel/plugin-syntax-optional-chaining" "^7.8.3" + "@babel/plugin-syntax-top-level-await" "^7.8.3" + +babel-preset-jest@^29.6.3: + version "29.6.3" + resolved "https://registry.yarnpkg.com/babel-preset-jest/-/babel-preset-jest-29.6.3.tgz#fa05fa510e7d493896d7b0dd2033601c840f171c" + integrity sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA== + dependencies: + babel-plugin-jest-hoist "^29.6.3" + babel-preset-current-node-syntax "^1.0.0" + +balanced-match@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" + integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== + +brace-expansion@^1.1.7: + version "1.1.11" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" + integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== + dependencies: + balanced-match "^1.0.0" + concat-map "0.0.1" + +braces@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.3.tgz#490332f40919452272d55a8480adc0c441358789" + integrity sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA== + dependencies: + fill-range "^7.1.1" + +browserslist@^4.22.2: + version "4.23.0" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.23.0.tgz#8f3acc2bbe73af7213399430890f86c63a5674ab" + integrity sha512-QW8HiM1shhT2GuzkvklfjcKDiWFXHOeFCIA/huJPwHsslwcydgk7X+z2zXpEijP98UCY7HbubZt5J2Zgvf0CaQ== + dependencies: + caniuse-lite "^1.0.30001587" + electron-to-chromium "^1.4.668" + node-releases "^2.0.14" + update-browserslist-db "^1.0.13" + +bs-logger@0.x: + version "0.2.6" + resolved "https://registry.yarnpkg.com/bs-logger/-/bs-logger-0.2.6.tgz#eb7d365307a72cf974cc6cda76b68354ad336bd8" + integrity sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog== + dependencies: + fast-json-stable-stringify "2.x" + +bser@2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/bser/-/bser-2.1.1.tgz#e6787da20ece9d07998533cfd9de6f5c38f4bc05" + integrity sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ== + dependencies: + node-int64 "^0.4.0" + +buffer-from@^1.0.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" + integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== + +callsites@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" + integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== + +camelcase@^5.3.1: + version "5.3.1" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" + integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== + +camelcase@^6.2.0: + version "6.3.0" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a" + integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== + +caniuse-lite@^1.0.30001587: + version "1.0.30001625" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001625.tgz#ead1b155ea691d6a87938754d3cb119c24465b03" + integrity sha512-4KE9N2gcRH+HQhpeiRZXd+1niLB/XNLAhSy4z7fI8EzcbcPoAqjNInxVHTiTwWfTIV4w096XG8OtCOCQQKPv3w== + +chalk@^2.4.2: + version "2.4.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" + integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== + dependencies: + ansi-styles "^3.2.1" + escape-string-regexp "^1.0.5" + supports-color "^5.3.0" + +chalk@^4.0.0: + version "4.1.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" + integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" + +char-regex@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/char-regex/-/char-regex-1.0.2.tgz#d744358226217f981ed58f479b1d6bcc29545dcf" + integrity sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw== + +ci-info@^3.2.0: + version "3.9.0" + resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.9.0.tgz#4279a62028a7b1f262f3473fc9605f5e218c59b4" + integrity sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ== + +cjs-module-lexer@^1.0.0: + version "1.3.1" + resolved "https://registry.yarnpkg.com/cjs-module-lexer/-/cjs-module-lexer-1.3.1.tgz#c485341ae8fd999ca4ee5af2d7a1c9ae01e0099c" + integrity sha512-a3KdPAANPbNE4ZUv9h6LckSl9zLsYOP4MBmhIPkRaeyybt+r4UghLvq+xw/YwUcC1gqylCkL4rdVs3Lwupjm4Q== + +cliui@^8.0.1: + version "8.0.1" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-8.0.1.tgz#0c04b075db02cbfe60dc8e6cf2f5486b1a3608aa" + integrity sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ== + dependencies: + string-width "^4.2.0" + strip-ansi "^6.0.1" + wrap-ansi "^7.0.0" + +co@^4.6.0: + version "4.6.0" + resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184" + integrity sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ== + +collect-v8-coverage@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/collect-v8-coverage/-/collect-v8-coverage-1.0.2.tgz#c0b29bcd33bcd0779a1344c2136051e6afd3d9e9" + integrity sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q== + +color-convert@^1.9.0: + version "1.9.3" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" + integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== + dependencies: + color-name "1.1.3" + +color-convert@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" + integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== + dependencies: + color-name "~1.1.4" + +color-name@1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" + integrity sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw== + +color-name@~1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" + integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== + +concat-map@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" + integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== + +convert-source-map@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-2.0.0.tgz#4b560f649fc4e918dd0ab75cf4961e8bc882d82a" + integrity sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg== + +create-jest@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/create-jest/-/create-jest-29.7.0.tgz#a355c5b3cb1e1af02ba177fe7afd7feee49a5320" + integrity sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q== + dependencies: + "@jest/types" "^29.6.3" + chalk "^4.0.0" + exit "^0.1.2" + graceful-fs "^4.2.9" + jest-config "^29.7.0" + jest-util "^29.7.0" + prompts "^2.0.1" + +create-require@^1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/create-require/-/create-require-1.1.1.tgz#c1d7e8f1e5f6cfc9ff65f9cd352d37348756c333" + integrity sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ== + +cross-spawn@^7.0.3: + version "7.0.3" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" + integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== + dependencies: + path-key "^3.1.0" + shebang-command "^2.0.0" + which "^2.0.1" + +debug@^4.1.0, debug@^4.1.1, debug@^4.3.1: + version "4.3.4" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" + integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== + dependencies: + ms "2.1.2" + +dedent@^1.0.0: + version "1.5.3" + resolved "https://registry.yarnpkg.com/dedent/-/dedent-1.5.3.tgz#99aee19eb9bae55a67327717b6e848d0bf777e5a" + integrity sha512-NHQtfOOW68WD8lgypbLA5oT+Bt0xXJhiYvoR6SmmNXZfpzOGXwdKWmcwG8N7PwVVWV3eF/68nmD9BaJSsTBhyQ== + +deepmerge@^4.2.2: + version "4.3.1" + resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.3.1.tgz#44b5f2147cd3b00d4b56137685966f26fd25dd4a" + integrity sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A== + +detect-newline@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-3.1.0.tgz#576f5dfc63ae1a192ff192d8ad3af6308991b651" + integrity sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA== + +diff-sequences@^29.6.3: + version "29.6.3" + resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-29.6.3.tgz#4deaf894d11407c51efc8418012f9e70b84ea921" + integrity sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q== + +diff@^4.0.1: + version "4.0.2" + resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d" + integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A== + +electron-to-chromium@^1.4.668: + version "1.4.786" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.786.tgz#974d7eeac61c5ffa285dc55555d06b97b05f6831" + integrity sha512-i/A2UB0sxYViMN0M2zIotQFRIOt1jLuVXudACHBDiJ5gGuAUzf/crZxwlBTdA0O52Hy4CNtTzS7AKRAacs/08Q== + +emittery@^0.13.1: + version "0.13.1" + resolved "https://registry.yarnpkg.com/emittery/-/emittery-0.13.1.tgz#c04b8c3457490e0847ae51fced3af52d338e3dad" + integrity sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ== + +emoji-regex@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" + integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== + +error-ex@^1.3.1: + version "1.3.2" + resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf" + integrity sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g== + dependencies: + is-arrayish "^0.2.1" + +escalade@^3.1.1, escalade@^3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.2.tgz#54076e9ab29ea5bf3d8f1ed62acffbb88272df27" + integrity sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA== + +escape-string-regexp@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" + integrity sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg== + +escape-string-regexp@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz#a30304e99daa32e23b2fd20f51babd07cffca344" + integrity sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w== + +esprima@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" + integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== + +execa@^5.0.0: + version "5.1.1" + resolved "https://registry.yarnpkg.com/execa/-/execa-5.1.1.tgz#f80ad9cbf4298f7bd1d4c9555c21e93741c411dd" + integrity sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg== + dependencies: + cross-spawn "^7.0.3" + get-stream "^6.0.0" + human-signals "^2.1.0" + is-stream "^2.0.0" + merge-stream "^2.0.0" + npm-run-path "^4.0.1" + onetime "^5.1.2" + signal-exit "^3.0.3" + strip-final-newline "^2.0.0" + +exit@^0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/exit/-/exit-0.1.2.tgz#0632638f8d877cc82107d30a0fff1a17cba1cd0c" + integrity sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ== + +expect@^29.0.0, expect@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/expect/-/expect-29.7.0.tgz#578874590dcb3214514084c08115d8aee61e11bc" + integrity sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw== + dependencies: + "@jest/expect-utils" "^29.7.0" + jest-get-type "^29.6.3" + jest-matcher-utils "^29.7.0" + jest-message-util "^29.7.0" + jest-util "^29.7.0" + +fast-json-stable-stringify@2.x, fast-json-stable-stringify@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" + integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== + +fb-watchman@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/fb-watchman/-/fb-watchman-2.0.2.tgz#e9524ee6b5c77e9e5001af0f85f3adbb8623255c" + integrity sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA== + dependencies: + bser "2.1.1" + +fill-range@^7.1.1: + version "7.1.1" + resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.1.1.tgz#44265d3cac07e3ea7dc247516380643754a05292" + integrity sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg== + dependencies: + to-regex-range "^5.0.1" + +find-up@^4.0.0, find-up@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19" + integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw== + dependencies: + locate-path "^5.0.0" + path-exists "^4.0.0" + +fs.realpath@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" + integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== + +fsevents@^2.3.2: + version "2.3.3" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6" + integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== + +function-bind@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c" + integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA== + +gensync@^1.0.0-beta.2: + version "1.0.0-beta.2" + resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0" + integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg== + +get-caller-file@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" + integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== + +get-package-type@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/get-package-type/-/get-package-type-0.1.0.tgz#8de2d803cff44df3bc6c456e6668b36c3926e11a" + integrity sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q== + +get-stream@^6.0.0: + version "6.0.1" + resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7" + integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg== + +glob@^7.1.3, glob@^7.1.4: + version "7.2.3" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" + integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.1.1" + once "^1.3.0" + path-is-absolute "^1.0.0" + +globals@^11.1.0: + version "11.12.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" + integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== + +graceful-fs@^4.2.9: + version "4.2.11" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" + integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== + +has-flag@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" + integrity sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw== + +has-flag@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" + integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== + +hasown@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.2.tgz#003eaf91be7adc372e84ec59dc37252cedb80003" + integrity sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ== + dependencies: + function-bind "^1.1.2" + +html-escaper@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/html-escaper/-/html-escaper-2.0.2.tgz#dfd60027da36a36dfcbe236262c00a5822681453" + integrity sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg== + +human-signals@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0" + integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw== + +import-local@^3.0.2: + version "3.1.0" + resolved "https://registry.yarnpkg.com/import-local/-/import-local-3.1.0.tgz#b4479df8a5fd44f6cdce24070675676063c95cb4" + integrity sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg== + dependencies: + pkg-dir "^4.2.0" + resolve-cwd "^3.0.0" + +imurmurhash@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" + integrity sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA== + +inflight@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" + integrity sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA== + dependencies: + once "^1.3.0" + wrappy "1" + +inherits@2: + version "2.0.4" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" + integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== + +is-arrayish@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" + integrity sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg== + +is-core-module@^2.13.0: + version "2.13.1" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.13.1.tgz#ad0d7532c6fea9da1ebdc82742d74525c6273384" + integrity sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw== + dependencies: + hasown "^2.0.0" + +is-fullwidth-code-point@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" + integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== + +is-generator-fn@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-generator-fn/-/is-generator-fn-2.1.0.tgz#7d140adc389aaf3011a8f2a2a4cfa6faadffb118" + integrity sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ== + +is-number@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" + integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== + +is-stream@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.1.tgz#fac1e3d53b97ad5a9d0ae9cef2389f5810a5c077" + integrity sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg== + +isexe@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" + integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== + +istanbul-lib-coverage@^3.0.0, istanbul-lib-coverage@^3.2.0: + version "3.2.2" + resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz#2d166c4b0644d43a39f04bf6c2edd1e585f31756" + integrity sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg== + +istanbul-lib-instrument@^5.0.4: + version "5.2.1" + resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz#d10c8885c2125574e1c231cacadf955675e1ce3d" + integrity sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg== + dependencies: + "@babel/core" "^7.12.3" + "@babel/parser" "^7.14.7" + "@istanbuljs/schema" "^0.1.2" + istanbul-lib-coverage "^3.2.0" + semver "^6.3.0" + +istanbul-lib-instrument@^6.0.0: + version "6.0.2" + resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.2.tgz#91655936cf7380e4e473383081e38478b69993b1" + integrity sha512-1WUsZ9R1lA0HtBSohTkm39WTPlNKSJ5iFk7UwqXkBLoHQT+hfqPsfsTDVuZdKGaBwn7din9bS7SsnoAr943hvw== + dependencies: + "@babel/core" "^7.23.9" + "@babel/parser" "^7.23.9" + "@istanbuljs/schema" "^0.1.3" + istanbul-lib-coverage "^3.2.0" + semver "^7.5.4" + +istanbul-lib-report@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz#908305bac9a5bd175ac6a74489eafd0fc2445a7d" + integrity sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw== + dependencies: + istanbul-lib-coverage "^3.0.0" + make-dir "^4.0.0" + supports-color "^7.1.0" + +istanbul-lib-source-maps@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz#895f3a709fcfba34c6de5a42939022f3e4358551" + integrity sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw== + dependencies: + debug "^4.1.1" + istanbul-lib-coverage "^3.0.0" + source-map "^0.6.1" + +istanbul-reports@^3.1.3: + version "3.1.7" + resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-3.1.7.tgz#daed12b9e1dca518e15c056e1e537e741280fa0b" + integrity sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g== + dependencies: + html-escaper "^2.0.0" + istanbul-lib-report "^3.0.0" + +jest-changed-files@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-29.7.0.tgz#1c06d07e77c78e1585d020424dedc10d6e17ac3a" + integrity sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w== + dependencies: + execa "^5.0.0" + jest-util "^29.7.0" + p-limit "^3.1.0" + +jest-circus@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-circus/-/jest-circus-29.7.0.tgz#b6817a45fcc835d8b16d5962d0c026473ee3668a" + integrity sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw== + dependencies: + "@jest/environment" "^29.7.0" + "@jest/expect" "^29.7.0" + "@jest/test-result" "^29.7.0" + "@jest/types" "^29.6.3" + "@types/node" "*" + chalk "^4.0.0" + co "^4.6.0" + dedent "^1.0.0" + is-generator-fn "^2.0.0" + jest-each "^29.7.0" + jest-matcher-utils "^29.7.0" + jest-message-util "^29.7.0" + jest-runtime "^29.7.0" + jest-snapshot "^29.7.0" + jest-util "^29.7.0" + p-limit "^3.1.0" + pretty-format "^29.7.0" + pure-rand "^6.0.0" + slash "^3.0.0" + stack-utils "^2.0.3" + +jest-cli@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-29.7.0.tgz#5592c940798e0cae677eec169264f2d839a37995" + integrity sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg== + dependencies: + "@jest/core" "^29.7.0" + "@jest/test-result" "^29.7.0" + "@jest/types" "^29.6.3" + chalk "^4.0.0" + create-jest "^29.7.0" + exit "^0.1.2" + import-local "^3.0.2" + jest-config "^29.7.0" + jest-util "^29.7.0" + jest-validate "^29.7.0" + yargs "^17.3.1" + +jest-config@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-29.7.0.tgz#bcbda8806dbcc01b1e316a46bb74085a84b0245f" + integrity sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ== + dependencies: + "@babel/core" "^7.11.6" + "@jest/test-sequencer" "^29.7.0" + "@jest/types" "^29.6.3" + babel-jest "^29.7.0" + chalk "^4.0.0" + ci-info "^3.2.0" + deepmerge "^4.2.2" + glob "^7.1.3" + graceful-fs "^4.2.9" + jest-circus "^29.7.0" + jest-environment-node "^29.7.0" + jest-get-type "^29.6.3" + jest-regex-util "^29.6.3" + jest-resolve "^29.7.0" + jest-runner "^29.7.0" + jest-util "^29.7.0" + jest-validate "^29.7.0" + micromatch "^4.0.4" + parse-json "^5.2.0" + pretty-format "^29.7.0" + slash "^3.0.0" + strip-json-comments "^3.1.1" + +jest-diff@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-29.7.0.tgz#017934a66ebb7ecf6f205e84699be10afd70458a" + integrity sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw== + dependencies: + chalk "^4.0.0" + diff-sequences "^29.6.3" + jest-get-type "^29.6.3" + pretty-format "^29.7.0" + +jest-docblock@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-29.7.0.tgz#8fddb6adc3cdc955c93e2a87f61cfd350d5d119a" + integrity sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g== + dependencies: + detect-newline "^3.0.0" + +jest-each@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-each/-/jest-each-29.7.0.tgz#162a9b3f2328bdd991beaabffbb74745e56577d1" + integrity sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ== + dependencies: + "@jest/types" "^29.6.3" + chalk "^4.0.0" + jest-get-type "^29.6.3" + jest-util "^29.7.0" + pretty-format "^29.7.0" + +jest-environment-node@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-29.7.0.tgz#0b93e111dda8ec120bc8300e6d1fb9576e164376" + integrity sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw== + dependencies: + "@jest/environment" "^29.7.0" + "@jest/fake-timers" "^29.7.0" + "@jest/types" "^29.6.3" + "@types/node" "*" + jest-mock "^29.7.0" + jest-util "^29.7.0" + +jest-get-type@^29.6.3: + version "29.6.3" + resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-29.6.3.tgz#36f499fdcea197c1045a127319c0481723908fd1" + integrity sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw== + +jest-haste-map@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-29.7.0.tgz#3c2396524482f5a0506376e6c858c3bbcc17b104" + integrity sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA== + dependencies: + "@jest/types" "^29.6.3" + "@types/graceful-fs" "^4.1.3" + "@types/node" "*" + anymatch "^3.0.3" + fb-watchman "^2.0.0" + graceful-fs "^4.2.9" + jest-regex-util "^29.6.3" + jest-util "^29.7.0" + jest-worker "^29.7.0" + micromatch "^4.0.4" + walker "^1.0.8" + optionalDependencies: + fsevents "^2.3.2" + +jest-leak-detector@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-29.7.0.tgz#5b7ec0dadfdfec0ca383dc9aa016d36b5ea4c728" + integrity sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw== + dependencies: + jest-get-type "^29.6.3" + pretty-format "^29.7.0" + +jest-matcher-utils@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz#ae8fec79ff249fd592ce80e3ee474e83a6c44f12" + integrity sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g== + dependencies: + chalk "^4.0.0" + jest-diff "^29.7.0" + jest-get-type "^29.6.3" + pretty-format "^29.7.0" + +jest-message-util@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-29.7.0.tgz#8bc392e204e95dfe7564abbe72a404e28e51f7f3" + integrity sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w== + dependencies: + "@babel/code-frame" "^7.12.13" + "@jest/types" "^29.6.3" + "@types/stack-utils" "^2.0.0" + chalk "^4.0.0" + graceful-fs "^4.2.9" + micromatch "^4.0.4" + pretty-format "^29.7.0" + slash "^3.0.0" + stack-utils "^2.0.3" + +jest-mock@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-29.7.0.tgz#4e836cf60e99c6fcfabe9f99d017f3fdd50a6347" + integrity sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw== + dependencies: + "@jest/types" "^29.6.3" + "@types/node" "*" + jest-util "^29.7.0" + +jest-pnp-resolver@^1.2.2: + version "1.2.3" + resolved "https://registry.yarnpkg.com/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz#930b1546164d4ad5937d5540e711d4d38d4cad2e" + integrity sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w== + +jest-regex-util@^29.6.3: + version "29.6.3" + resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-29.6.3.tgz#4a556d9c776af68e1c5f48194f4d0327d24e8a52" + integrity sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg== + +jest-resolve-dependencies@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-29.7.0.tgz#1b04f2c095f37fc776ff40803dc92921b1e88428" + integrity sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA== + dependencies: + jest-regex-util "^29.6.3" + jest-snapshot "^29.7.0" + +jest-resolve@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-29.7.0.tgz#64d6a8992dd26f635ab0c01e5eef4399c6bcbc30" + integrity sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA== + dependencies: + chalk "^4.0.0" + graceful-fs "^4.2.9" + jest-haste-map "^29.7.0" + jest-pnp-resolver "^1.2.2" + jest-util "^29.7.0" + jest-validate "^29.7.0" + resolve "^1.20.0" + resolve.exports "^2.0.0" + slash "^3.0.0" + +jest-runner@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-29.7.0.tgz#809af072d408a53dcfd2e849a4c976d3132f718e" + integrity sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ== + dependencies: + "@jest/console" "^29.7.0" + "@jest/environment" "^29.7.0" + "@jest/test-result" "^29.7.0" + "@jest/transform" "^29.7.0" + "@jest/types" "^29.6.3" + "@types/node" "*" + chalk "^4.0.0" + emittery "^0.13.1" + graceful-fs "^4.2.9" + jest-docblock "^29.7.0" + jest-environment-node "^29.7.0" + jest-haste-map "^29.7.0" + jest-leak-detector "^29.7.0" + jest-message-util "^29.7.0" + jest-resolve "^29.7.0" + jest-runtime "^29.7.0" + jest-util "^29.7.0" + jest-watcher "^29.7.0" + jest-worker "^29.7.0" + p-limit "^3.1.0" + source-map-support "0.5.13" + +jest-runtime@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-29.7.0.tgz#efecb3141cf7d3767a3a0cc8f7c9990587d3d817" + integrity sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ== + dependencies: + "@jest/environment" "^29.7.0" + "@jest/fake-timers" "^29.7.0" + "@jest/globals" "^29.7.0" + "@jest/source-map" "^29.6.3" + "@jest/test-result" "^29.7.0" + "@jest/transform" "^29.7.0" + "@jest/types" "^29.6.3" + "@types/node" "*" + chalk "^4.0.0" + cjs-module-lexer "^1.0.0" + collect-v8-coverage "^1.0.0" + glob "^7.1.3" + graceful-fs "^4.2.9" + jest-haste-map "^29.7.0" + jest-message-util "^29.7.0" + jest-mock "^29.7.0" + jest-regex-util "^29.6.3" + jest-resolve "^29.7.0" + jest-snapshot "^29.7.0" + jest-util "^29.7.0" + slash "^3.0.0" + strip-bom "^4.0.0" + +jest-snapshot@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-29.7.0.tgz#c2c574c3f51865da1bb329036778a69bf88a6be5" + integrity sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw== + dependencies: + "@babel/core" "^7.11.6" + "@babel/generator" "^7.7.2" + "@babel/plugin-syntax-jsx" "^7.7.2" + "@babel/plugin-syntax-typescript" "^7.7.2" + "@babel/types" "^7.3.3" + "@jest/expect-utils" "^29.7.0" + "@jest/transform" "^29.7.0" + "@jest/types" "^29.6.3" + babel-preset-current-node-syntax "^1.0.0" + chalk "^4.0.0" + expect "^29.7.0" + graceful-fs "^4.2.9" + jest-diff "^29.7.0" + jest-get-type "^29.6.3" + jest-matcher-utils "^29.7.0" + jest-message-util "^29.7.0" + jest-util "^29.7.0" + natural-compare "^1.4.0" + pretty-format "^29.7.0" + semver "^7.5.3" + +jest-util@^29.0.0, jest-util@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-29.7.0.tgz#23c2b62bfb22be82b44de98055802ff3710fc0bc" + integrity sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA== + dependencies: + "@jest/types" "^29.6.3" + "@types/node" "*" + chalk "^4.0.0" + ci-info "^3.2.0" + graceful-fs "^4.2.9" + picomatch "^2.2.3" + +jest-validate@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-29.7.0.tgz#7bf705511c64da591d46b15fce41400d52147d9c" + integrity sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw== + dependencies: + "@jest/types" "^29.6.3" + camelcase "^6.2.0" + chalk "^4.0.0" + jest-get-type "^29.6.3" + leven "^3.1.0" + pretty-format "^29.7.0" + +jest-watcher@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-watcher/-/jest-watcher-29.7.0.tgz#7810d30d619c3a62093223ce6bb359ca1b28a2f2" + integrity sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g== + dependencies: + "@jest/test-result" "^29.7.0" + "@jest/types" "^29.6.3" + "@types/node" "*" + ansi-escapes "^4.2.1" + chalk "^4.0.0" + emittery "^0.13.1" + jest-util "^29.7.0" + string-length "^4.0.1" + +jest-worker@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-29.7.0.tgz#acad073acbbaeb7262bd5389e1bcf43e10058d4a" + integrity sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw== + dependencies: + "@types/node" "*" + jest-util "^29.7.0" + merge-stream "^2.0.0" + supports-color "^8.0.0" + +jest@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest/-/jest-29.7.0.tgz#994676fc24177f088f1c5e3737f5697204ff2613" + integrity sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw== + dependencies: + "@jest/core" "^29.7.0" + "@jest/types" "^29.6.3" + import-local "^3.0.2" + jest-cli "^29.7.0" + +js-tokens@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" + integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== + +js-yaml@^3.13.1: + version "3.14.1" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.1.tgz#dae812fdb3825fa306609a8717383c50c36a0537" + integrity sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g== + dependencies: + argparse "^1.0.7" + esprima "^4.0.0" + +jsesc@^2.5.1: + version "2.5.2" + resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4" + integrity sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA== + +json-parse-even-better-errors@^2.3.0: + version "2.3.1" + resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d" + integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w== + +json5@^2.2.2, json5@^2.2.3: + version "2.2.3" + resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283" + integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== + +kleur@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e" + integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w== + +leven@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/leven/-/leven-3.1.0.tgz#77891de834064cccba82ae7842bb6b14a13ed7f2" + integrity sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A== + +lines-and-columns@^1.1.6: + version "1.2.4" + resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632" + integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg== + +locate-path@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-5.0.0.tgz#1afba396afd676a6d42504d0a67a3a7eb9f62aa0" + integrity sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g== + dependencies: + p-locate "^4.1.0" + +lodash.memoize@4.x: + version "4.1.2" + resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" + integrity sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag== + +lru-cache@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920" + integrity sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w== + dependencies: + yallist "^3.0.2" + +make-dir@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-4.0.0.tgz#c3c2307a771277cd9638305f915c29ae741b614e" + integrity sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw== + dependencies: + semver "^7.5.3" + +make-error@1.x, make-error@^1.1.1: + version "1.3.6" + resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2" + integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw== + +makeerror@1.0.12: + version "1.0.12" + resolved "https://registry.yarnpkg.com/makeerror/-/makeerror-1.0.12.tgz#3e5dd2079a82e812e983cc6610c4a2cb0eaa801a" + integrity sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg== + dependencies: + tmpl "1.0.5" + +merge-stream@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" + integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== + +micromatch@^4.0.4: + version "4.0.7" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.7.tgz#33e8190d9fe474a9895525f5618eee136d46c2e5" + integrity sha512-LPP/3KorzCwBxfeUuZmaR6bG2kdeHSbe0P2tY3FLRU4vYrjYz5hI4QZwV0njUx3jeuKe67YukQ1LSPZBKDqO/Q== + dependencies: + braces "^3.0.3" + picomatch "^2.3.1" + +mimic-fn@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" + integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== + +minimatch@^3.0.4, minimatch@^3.1.1: + version "3.1.2" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" + integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== + dependencies: + brace-expansion "^1.1.7" + +minimist@^1.2.6: + version "1.2.8" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c" + integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== + +ms@2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" + integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== + +natural-compare@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" + integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw== + +node-int64@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b" + integrity sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw== + +node-releases@^2.0.14: + version "2.0.14" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.14.tgz#2ffb053bceb8b2be8495ece1ab6ce600c4461b0b" + integrity sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw== + +normalize-path@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" + integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== + +npm-run-path@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-4.0.1.tgz#b7ecd1e5ed53da8e37a55e1c2269e0b97ed748ea" + integrity sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw== + dependencies: + path-key "^3.0.0" + +once@^1.3.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" + integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== + dependencies: + wrappy "1" + +onetime@^5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.2.tgz#d0e96ebb56b07476df1dd9c4806e5237985ca45e" + integrity sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg== + dependencies: + mimic-fn "^2.1.0" + +p-limit@^2.2.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1" + integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w== + dependencies: + p-try "^2.0.0" + +p-limit@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b" + integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ== + dependencies: + yocto-queue "^0.1.0" + +p-locate@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-4.1.0.tgz#a3428bb7088b3a60292f66919278b7c297ad4f07" + integrity sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A== + dependencies: + p-limit "^2.2.0" + +p-try@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" + integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== + +parse-json@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-5.2.0.tgz#c76fc66dee54231c962b22bcc8a72cf2f99753cd" + integrity sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg== + dependencies: + "@babel/code-frame" "^7.0.0" + error-ex "^1.3.1" + json-parse-even-better-errors "^2.3.0" + lines-and-columns "^1.1.6" + +path-exists@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" + integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== + +path-is-absolute@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" + integrity sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg== + +path-key@^3.0.0, path-key@^3.1.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" + integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== + +path-parse@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" + integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== + +picocolors@^1.0.0, picocolors@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.1.tgz#a8ad579b571952f0e5d25892de5445bcfe25aaa1" + integrity sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew== + +picomatch@^2.0.4, picomatch@^2.2.3, picomatch@^2.3.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" + integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== + +pirates@^4.0.4: + version "4.0.6" + resolved "https://registry.yarnpkg.com/pirates/-/pirates-4.0.6.tgz#3018ae32ecfcff6c29ba2267cbf21166ac1f36b9" + integrity sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg== + +pkg-dir@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-4.2.0.tgz#f099133df7ede422e81d1d8448270eeb3e4261f3" + integrity sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ== + dependencies: + find-up "^4.0.0" + +pretty-format@^29.0.0, pretty-format@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-29.7.0.tgz#ca42c758310f365bfa71a0bda0a807160b776812" + integrity sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ== + dependencies: + "@jest/schemas" "^29.6.3" + ansi-styles "^5.0.0" + react-is "^18.0.0" + +prompts@^2.0.1: + version "2.4.2" + resolved "https://registry.yarnpkg.com/prompts/-/prompts-2.4.2.tgz#7b57e73b3a48029ad10ebd44f74b01722a4cb069" + integrity sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q== + dependencies: + kleur "^3.0.3" + sisteransi "^1.0.5" + +pure-rand@^6.0.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/pure-rand/-/pure-rand-6.1.0.tgz#d173cf23258231976ccbdb05247c9787957604f2" + integrity sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA== + +react-is@^18.0.0: + version "18.3.1" + resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.3.1.tgz#e83557dc12eae63a99e003a46388b1dcbb44db7e" + integrity sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg== + +require-directory@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" + integrity sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q== + +resolve-cwd@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-3.0.0.tgz#0f0075f1bb2544766cf73ba6a6e2adfebcb13f2d" + integrity sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg== + dependencies: + resolve-from "^5.0.0" + +resolve-from@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-5.0.0.tgz#c35225843df8f776df21c57557bc087e9dfdfc69" + integrity sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw== + +resolve.exports@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/resolve.exports/-/resolve.exports-2.0.2.tgz#f8c934b8e6a13f539e38b7098e2e36134f01e800" + integrity sha512-X2UW6Nw3n/aMgDVy+0rSqgHlv39WZAlZrXCdnbyEiKm17DSqHX4MmQMaST3FbeWR5FTuRcUwYAziZajji0Y7mg== + +resolve@^1.20.0: + version "1.22.8" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.8.tgz#b6c87a9f2aa06dfab52e3d70ac8cde321fa5a48d" + integrity sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw== + dependencies: + is-core-module "^2.13.0" + path-parse "^1.0.7" + supports-preserve-symlinks-flag "^1.0.0" + +semver@^6.3.0, semver@^6.3.1: + version "6.3.1" + resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" + integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== + +semver@^7.5.3, semver@^7.5.4: + version "7.6.2" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.2.tgz#1e3b34759f896e8f14d6134732ce798aeb0c6e13" + integrity sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w== + +shebang-command@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" + integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== + dependencies: + shebang-regex "^3.0.0" + +shebang-regex@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" + integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== + +signal-exit@^3.0.3, signal-exit@^3.0.7: + version "3.0.7" + resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" + integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== + +sisteransi@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/sisteransi/-/sisteransi-1.0.5.tgz#134d681297756437cc05ca01370d3a7a571075ed" + integrity sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg== + +slash@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" + integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== + +source-map-support@0.5.13: + version "0.5.13" + resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.13.tgz#31b24a9c2e73c2de85066c0feb7d44767ed52932" + integrity sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w== + dependencies: + buffer-from "^1.0.0" + source-map "^0.6.0" + +source-map@^0.6.0, source-map@^0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" + integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== + +sprintf-js@~1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" + integrity sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g== + +stack-utils@^2.0.3: + version "2.0.6" + resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-2.0.6.tgz#aaf0748169c02fc33c8232abccf933f54a1cc34f" + integrity sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ== + dependencies: + escape-string-regexp "^2.0.0" + +string-length@^4.0.1: + version "4.0.2" + resolved "https://registry.yarnpkg.com/string-length/-/string-length-4.0.2.tgz#a8a8dc7bd5c1a82b9b3c8b87e125f66871b6e57a" + integrity sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ== + dependencies: + char-regex "^1.0.2" + strip-ansi "^6.0.0" + +string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: + version "4.2.3" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + +strip-ansi@^6.0.0, strip-ansi@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + +strip-bom@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3" + integrity sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA== + +strip-bom@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-4.0.0.tgz#9c3505c1db45bcedca3d9cf7a16f5c5aa3901878" + integrity sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w== + +strip-final-newline@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad" + integrity sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA== + +strip-json-comments@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" + integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== + +supports-color@^5.3.0: + version "5.5.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" + integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== + dependencies: + has-flag "^3.0.0" + +supports-color@^7.1.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" + integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== + dependencies: + has-flag "^4.0.0" + +supports-color@^8.0.0: + version "8.1.1" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-8.1.1.tgz#cd6fc17e28500cff56c1b86c0a7fd4a54a73005c" + integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q== + dependencies: + has-flag "^4.0.0" + +supports-preserve-symlinks-flag@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" + integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== + +test-exclude@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/test-exclude/-/test-exclude-6.0.0.tgz#04a8698661d805ea6fa293b6cb9e63ac044ef15e" + integrity sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w== + dependencies: + "@istanbuljs/schema" "^0.1.2" + glob "^7.1.4" + minimatch "^3.0.4" + +tmpl@1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/tmpl/-/tmpl-1.0.5.tgz#8683e0b902bb9c20c4f726e3c0b69f36518c07cc" + integrity sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw== + +to-fast-properties@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e" + integrity sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog== + +to-regex-range@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" + integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== + dependencies: + is-number "^7.0.0" + +ts-jest@^29.1.4: + version "29.1.4" + resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-29.1.4.tgz#26f8a55ce31e4d2ef7a1fd47dc7fa127e92793ef" + integrity sha512-YiHwDhSvCiItoAgsKtoLFCuakDzDsJ1DLDnSouTaTmdOcOwIkSzbLXduaQ6M5DRVhuZC/NYaaZ/mtHbWMv/S6Q== + dependencies: + bs-logger "0.x" + fast-json-stable-stringify "2.x" + jest-util "^29.0.0" + json5 "^2.2.3" + lodash.memoize "4.x" + make-error "1.x" + semver "^7.5.3" + yargs-parser "^21.0.1" + +ts-node@^10.9.2: + version "10.9.2" + resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-10.9.2.tgz#70f021c9e185bccdca820e26dc413805c101c71f" + integrity sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ== + dependencies: + "@cspotcode/source-map-support" "^0.8.0" + "@tsconfig/node10" "^1.0.7" + "@tsconfig/node12" "^1.0.7" + "@tsconfig/node14" "^1.0.0" + "@tsconfig/node16" "^1.0.2" + acorn "^8.4.1" + acorn-walk "^8.1.1" + arg "^4.1.0" + create-require "^1.1.0" + diff "^4.0.1" + make-error "^1.1.1" + v8-compile-cache-lib "^3.0.1" + yn "3.1.1" + +tsconfig-paths@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-4.2.0.tgz#ef78e19039133446d244beac0fd6a1632e2d107c" + integrity sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg== + dependencies: + json5 "^2.2.2" + minimist "^1.2.6" + strip-bom "^3.0.0" + +type-detect@4.0.8: + version "4.0.8" + resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c" + integrity sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g== + +type-fest@^0.21.3: + version "0.21.3" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.21.3.tgz#d260a24b0198436e133fa26a524a6d65fa3b2e37" + integrity sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w== + +typescript@^5.4.5: + version "5.4.5" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.4.5.tgz#42ccef2c571fdbd0f6718b1d1f5e6e5ef006f611" + integrity sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ== + +undici-types@~5.26.4: + version "5.26.5" + resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-5.26.5.tgz#bcd539893d00b56e964fd2657a4866b221a65617" + integrity sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA== + +update-browserslist-db@^1.0.13: + version "1.0.16" + resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.0.16.tgz#f6d489ed90fb2f07d67784eb3f53d7891f736356" + integrity sha512-KVbTxlBYlckhF5wgfyZXTWnMn7MMZjMu9XG8bPlliUOP9ThaF4QnhP8qrjrH7DRzHfSk0oQv1wToW+iA5GajEQ== + dependencies: + escalade "^3.1.2" + picocolors "^1.0.1" + +v8-compile-cache-lib@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz#6336e8d71965cb3d35a1bbb7868445a7c05264bf" + integrity sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg== + +v8-to-istanbul@^9.0.1: + version "9.2.0" + resolved "https://registry.yarnpkg.com/v8-to-istanbul/-/v8-to-istanbul-9.2.0.tgz#2ed7644a245cddd83d4e087b9b33b3e62dfd10ad" + integrity sha512-/EH/sDgxU2eGxajKdwLCDmQ4FWq+kpi3uCmBGpw1xJtnAxEjlD8j8PEiGWpCIMIs3ciNAgH0d3TTJiUkYzyZjA== + dependencies: + "@jridgewell/trace-mapping" "^0.3.12" + "@types/istanbul-lib-coverage" "^2.0.1" + convert-source-map "^2.0.0" + +walker@^1.0.8: + version "1.0.8" + resolved "https://registry.yarnpkg.com/walker/-/walker-1.0.8.tgz#bd498db477afe573dc04185f011d3ab8a8d7653f" + integrity sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ== + dependencies: + makeerror "1.0.12" + +which@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" + integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== + dependencies: + isexe "^2.0.0" + +wrap-ansi@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + +wrappy@1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" + integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== + +write-file-atomic@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-4.0.2.tgz#a9df01ae5b77858a027fd2e80768ee433555fcfd" + integrity sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg== + dependencies: + imurmurhash "^0.1.4" + signal-exit "^3.0.7" + +y18n@^5.0.5: + version "5.0.8" + resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55" + integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== + +yallist@^3.0.2: + version "3.1.1" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd" + integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g== + +yargs-parser@^21.0.1, yargs-parser@^21.1.1: + version "21.1.1" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.1.1.tgz#9096bceebf990d21bb31fa9516e0ede294a77d35" + integrity sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw== + +yargs@^17.3.1: + version "17.7.2" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.7.2.tgz#991df39aca675a192b816e1e0363f9d75d2aa269" + integrity sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w== + dependencies: + cliui "^8.0.1" + escalade "^3.1.1" + get-caller-file "^2.0.5" + require-directory "^2.1.1" + string-width "^4.2.3" + y18n "^5.0.5" + yargs-parser "^21.1.1" + +yn@3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50" + integrity sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q== + +yocto-queue@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" + integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== From 1dd0516f868e2d9eeec527ee382b79e4c8fd470f Mon Sep 17 00:00:00 2001 From: Leandro Reis Date: Fri, 31 May 2024 10:44:22 -0300 Subject: [PATCH 02/43] refactor: Remove Calculator class and update entry point to use async function The Calculator class was removed as it was no longer needed. It was used only to test the jest and typescript configuration --- src/index.ts | 7 ++++--- src/module/calculator.ts | 5 ----- test/module/calculator.test.ts | 8 -------- 3 files changed, 4 insertions(+), 16 deletions(-) delete mode 100644 src/module/calculator.ts delete mode 100644 test/module/calculator.test.ts diff --git a/src/index.ts b/src/index.ts index cd4da232d..93655f130 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,4 +1,5 @@ -import Calculator from "@/module/calculator"; +async function main() { + // This is the entry point of the application +} -const calculator = new Calculator() -console.log("40 + 2 =", calculator.add(40, 2)); \ No newline at end of file +main().catch(console.error); \ No newline at end of file diff --git a/src/module/calculator.ts b/src/module/calculator.ts deleted file mode 100644 index fa614acf0..000000000 --- a/src/module/calculator.ts +++ /dev/null @@ -1,5 +0,0 @@ -export default class Calculator { - add(a: number, b: number): number { - return a + b; - } -} \ No newline at end of file diff --git a/test/module/calculator.test.ts b/test/module/calculator.test.ts deleted file mode 100644 index 5e4d67321..000000000 --- a/test/module/calculator.test.ts +++ /dev/null @@ -1,8 +0,0 @@ -import Calculator from "@/module/calculator"; - -describe('Calculator', () => { - it('should add two numbers', () => { - const calculator = new Calculator(); - expect(calculator.add(40, 2)).toBe(42); - }); -}); \ No newline at end of file From 56fa796733048a7feea6f21ba7a1b229d3f57887 Mon Sep 17 00:00:00 2001 From: Leandro Reis Date: Fri, 31 May 2024 10:46:20 -0300 Subject: [PATCH 03/43] feat: implement the currency conversion --- package.json | 6 ++- .../gateway/exchange-rate-gateway.ts | 8 +++ src/application/use-case/convert-currency.ts | 25 +++++++++ src/domain/currency-converter.ts | 17 ++++++ src/domain/currency.ts | 22 ++++++++ .../gateway/exchange-rate-gateway-fake.ts | 18 +++++++ test/unity/convert-currency.test.ts | 53 +++++++++++++++++++ yarn.lock | 5 ++ 8 files changed, 152 insertions(+), 2 deletions(-) create mode 100644 src/application/gateway/exchange-rate-gateway.ts create mode 100644 src/application/use-case/convert-currency.ts create mode 100644 src/domain/currency-converter.ts create mode 100644 src/domain/currency.ts create mode 100644 src/infra/gateway/exchange-rate-gateway-fake.ts create mode 100644 test/unity/convert-currency.test.ts diff --git a/package.json b/package.json index 555a288b4..5dc2ea7d4 100644 --- a/package.json +++ b/package.json @@ -8,9 +8,11 @@ "scripts": { "dev": "ts-node -r tsconfig-paths/register src/index.ts", "test": "jest", - "test:watch": "jest --watch" + "test:watch": "jest --watch --detectOpenHandles" + }, + "dependencies": { + "currency.js": "^2.0.4" }, - "dependencies": {}, "devDependencies": { "@types/jest": "^29.5.12", "jest": "^29.7.0", diff --git a/src/application/gateway/exchange-rate-gateway.ts b/src/application/gateway/exchange-rate-gateway.ts new file mode 100644 index 000000000..a2542acf5 --- /dev/null +++ b/src/application/gateway/exchange-rate-gateway.ts @@ -0,0 +1,8 @@ +export default interface ExchangeRateGateway { + getExchangeRate(exchangeCode: string): Promise; +} + +export type CurrencyExchange = { + code: string; + rate: number; +} \ No newline at end of file diff --git a/src/application/use-case/convert-currency.ts b/src/application/use-case/convert-currency.ts new file mode 100644 index 000000000..b715f3fb6 --- /dev/null +++ b/src/application/use-case/convert-currency.ts @@ -0,0 +1,25 @@ +import ExchangeRateGateway from "@/application/gateway/exchange-rate-gateway"; +import CurrencyConverter from "@/domain/currency-converter"; + +export default class ConvertCurrency { + constructor(readonly exchangeRateGateway: ExchangeRateGateway) { + } + + async execute(input: Input): Promise { + const exchangeFrom = await this.exchangeRateGateway.getExchangeRate(input.from); + const exchangeTo = await this.exchangeRateGateway.getExchangeRate(input.to); + const currencyConverter = new CurrencyConverter(exchangeFrom.rate, exchangeTo.rate); + const convertedAmount = currencyConverter.convert(input.amount); + return { value: convertedAmount } + } +} + +type Input = { + from: string; + to: string; + amount: number; +} + +type Output = { + value: number; +} \ No newline at end of file diff --git a/src/domain/currency-converter.ts b/src/domain/currency-converter.ts new file mode 100644 index 000000000..4b75ed989 --- /dev/null +++ b/src/domain/currency-converter.ts @@ -0,0 +1,17 @@ +import Currency from '@/domain/currency'; + +export default class CurrencyConverter { + private _rateFrom: number; + private _rateTo: number; + + constructor(rateFrom: number, rateTo: number) { + this._rateFrom = rateFrom; + this._rateTo = rateTo; + } + + convert(amount: number): number { + const amountCurrency = new Currency(amount) + amountCurrency.multiply(this._rateTo / this._rateFrom); + return amountCurrency.value; + } +} \ No newline at end of file diff --git a/src/domain/currency.ts b/src/domain/currency.ts new file mode 100644 index 000000000..cda35b057 --- /dev/null +++ b/src/domain/currency.ts @@ -0,0 +1,22 @@ +import currency from "currency.js"; + +export default class Currency { + private _value: currency; + + constructor(value: number) { + if (typeof value !== 'number') { + throw new Error('Invalid value'); + } + this._value = currency(value); + } + + get value(): number { + return this._value.value; + } + + multiply(value: number): void { + this._value = this._value.multiply(value); + } +} + +// currency.js documentation: https://currency.js.org/ \ No newline at end of file diff --git a/src/infra/gateway/exchange-rate-gateway-fake.ts b/src/infra/gateway/exchange-rate-gateway-fake.ts new file mode 100644 index 000000000..81a5bb996 --- /dev/null +++ b/src/infra/gateway/exchange-rate-gateway-fake.ts @@ -0,0 +1,18 @@ +import ExchangeRateGateway, { CurrencyExchange } from "@/application/gateway/exchange-rate-gateway"; + +export class ExchangeRateGatewayFake implements ExchangeRateGateway { + private _rates: { [key: string]: number } = { + USD: 1, + BRL: 5.20, + EUR: 0.92, + }; + async getExchangeRate(exchangeCode: string): Promise { + if (this._rates[exchangeCode]) { + return { + code: exchangeCode, + rate: this._rates[exchangeCode], + } + } + throw new Error('Invalid exchange code'); + } +} \ No newline at end of file diff --git a/test/unity/convert-currency.test.ts b/test/unity/convert-currency.test.ts new file mode 100644 index 000000000..402f733c0 --- /dev/null +++ b/test/unity/convert-currency.test.ts @@ -0,0 +1,53 @@ +import ExchangeRateGateway from "@/application/gateway/exchange-rate-gateway"; +import ConvertCurrency from "@/application/use-case/convert-currency"; +import { ExchangeRateGatewayFake } from "@/infra/gateway/exchange-rate-gateway-fake"; + +describe('Currency Converter', () => { + let exchangeRateGateway: ExchangeRateGateway; + let convertCurrency: ConvertCurrency; + beforeEach(() => { + exchangeRateGateway = new ExchangeRateGatewayFake(); + convertCurrency = new ConvertCurrency(exchangeRateGateway); + }); + + it('should convert USD to BRL', async () => { + const input = { + from: 'USD', + to: 'BRL', + amount: 1, + } + const result = await convertCurrency.execute(input); + expect(result.value).toBe(5.20); + }); + + it('should convert BRL to USD', async () => { + const input = { + from: 'BRL', + to: 'USD', + amount: 5.20, + } + const result = await convertCurrency.execute(input); + expect(result.value).toBe(1.00); + }); + + it('should convert USD to EUR', async () => { + const input = { + from: 'USD', + to: 'EUR', + amount: 1, + } + const result = await convertCurrency.execute(input); + expect(result.value).toBe(0.92); + }); + + it('should convert EUR to BRL', async () => { + const input = { + from: 'EUR', + to: 'BRL', + amount: 1, + } + const result = await convertCurrency.execute(input); + expect(result.value).toBe(5.65); + }); + +}); \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index 000379e65..c4f9bff79 100644 --- a/yarn.lock +++ b/yarn.lock @@ -984,6 +984,11 @@ cross-spawn@^7.0.3: shebang-command "^2.0.0" which "^2.0.1" +currency.js@^2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/currency.js/-/currency.js-2.0.4.tgz#a8a4d69be3b2e509bf67a560c78220bc04809cf1" + integrity sha512-6/OplJYgJ0RUlli74d93HJ/OsKVBi8lB1+Z6eJYS1YZzBuIp4qKKHpJ7ad+GvTlWmLR/hLJOWTykN5Nm8NJ7+w== + debug@^4.1.0, debug@^4.1.1, debug@^4.3.1: version "4.3.4" resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" From 397b5fe9543debff109420664b21b31a3259c061 Mon Sep 17 00:00:00 2001 From: Leandro Reis Date: Fri, 31 May 2024 10:55:13 -0300 Subject: [PATCH 04/43] refactor: Ensure that gateway throws CurrencyNotFoundError when an invalid currency code is passed --- src/domain/erros/currency-not-found.ts | 6 ++++++ src/infra/gateway/exchange-rate-gateway-fake.ts | 3 ++- test/unity/convert-currency.test.ts | 11 +++++++++++ 3 files changed, 19 insertions(+), 1 deletion(-) create mode 100644 src/domain/erros/currency-not-found.ts diff --git a/src/domain/erros/currency-not-found.ts b/src/domain/erros/currency-not-found.ts new file mode 100644 index 000000000..c87d62a6b --- /dev/null +++ b/src/domain/erros/currency-not-found.ts @@ -0,0 +1,6 @@ +export default class CurrencyNotFoundError extends Error { + constructor(currencyCode: string) { + super(`Currency not found: ${currencyCode}`); + this.name = 'CurrencyNotFound'; + } +} \ No newline at end of file diff --git a/src/infra/gateway/exchange-rate-gateway-fake.ts b/src/infra/gateway/exchange-rate-gateway-fake.ts index 81a5bb996..6bd9aa28c 100644 --- a/src/infra/gateway/exchange-rate-gateway-fake.ts +++ b/src/infra/gateway/exchange-rate-gateway-fake.ts @@ -1,4 +1,5 @@ import ExchangeRateGateway, { CurrencyExchange } from "@/application/gateway/exchange-rate-gateway"; +import CurrencyNotFoundError from "@/domain/erros/currency-not-found"; export class ExchangeRateGatewayFake implements ExchangeRateGateway { private _rates: { [key: string]: number } = { @@ -13,6 +14,6 @@ export class ExchangeRateGatewayFake implements ExchangeRateGateway { rate: this._rates[exchangeCode], } } - throw new Error('Invalid exchange code'); + throw new CurrencyNotFoundError(exchangeCode); } } \ No newline at end of file diff --git a/test/unity/convert-currency.test.ts b/test/unity/convert-currency.test.ts index 402f733c0..2cb252935 100644 --- a/test/unity/convert-currency.test.ts +++ b/test/unity/convert-currency.test.ts @@ -1,5 +1,6 @@ import ExchangeRateGateway from "@/application/gateway/exchange-rate-gateway"; import ConvertCurrency from "@/application/use-case/convert-currency"; +import CurrencyNotFoundError from "@/domain/erros/currency-not-found"; import { ExchangeRateGatewayFake } from "@/infra/gateway/exchange-rate-gateway-fake"; describe('Currency Converter', () => { @@ -50,4 +51,14 @@ describe('Currency Converter', () => { expect(result.value).toBe(5.65); }); + it('should throw an error when the currency is not found', async () => { + const input = { + from: 'INVALID', + to: 'BRL', + amount: 1, + } + const currencyNotFoundError = new CurrencyNotFoundError('INVALID'); + await expect(convertCurrency.execute(input)).rejects.toThrow(currencyNotFoundError); + }); + }); \ No newline at end of file From b05bfe7bece9d538763ba1c39575afef9e93f9d9 Mon Sep 17 00:00:00 2001 From: Leandro Reis Date: Sun, 2 Jun 2024 12:09:24 -0300 Subject: [PATCH 05/43] feat: Add validation for negative amount in CurrencyConverter --- src/domain/currency-converter.ts | 3 +++ test/unity/convert-currency.test.ts | 8 ++++++++ 2 files changed, 11 insertions(+) diff --git a/src/domain/currency-converter.ts b/src/domain/currency-converter.ts index 4b75ed989..4788fa82a 100644 --- a/src/domain/currency-converter.ts +++ b/src/domain/currency-converter.ts @@ -10,6 +10,9 @@ export default class CurrencyConverter { } convert(amount: number): number { + if (amount < 0) { + throw new Error('Invalid amount'); + } const amountCurrency = new Currency(amount) amountCurrency.multiply(this._rateTo / this._rateFrom); return amountCurrency.value; diff --git a/test/unity/convert-currency.test.ts b/test/unity/convert-currency.test.ts index 2cb252935..84d3eb2b1 100644 --- a/test/unity/convert-currency.test.ts +++ b/test/unity/convert-currency.test.ts @@ -61,4 +61,12 @@ describe('Currency Converter', () => { await expect(convertCurrency.execute(input)).rejects.toThrow(currencyNotFoundError); }); + it('should throw an error when the amount is a negative number', async () => { + const input = { + from: 'USD', + to: 'BRL', + amount: -1, + } + await expect(convertCurrency.execute(input)).rejects.toThrow('Invalid amount'); + }); }); \ No newline at end of file From 96b3bfe76e31b36dbb6a77a86feab96e3c874085 Mon Sep 17 00:00:00 2001 From: Leandro Reis Date: Sun, 2 Jun 2024 12:49:55 -0300 Subject: [PATCH 06/43] refactor: Add currency converter unit test --- test/unity/convert-currency.test.ts | 13 ++----------- test/unity/currency-converter.test.ts | 25 +++++++++++++++++++++++++ 2 files changed, 27 insertions(+), 11 deletions(-) create mode 100644 test/unity/currency-converter.test.ts diff --git a/test/unity/convert-currency.test.ts b/test/unity/convert-currency.test.ts index 84d3eb2b1..5082517b8 100644 --- a/test/unity/convert-currency.test.ts +++ b/test/unity/convert-currency.test.ts @@ -1,11 +1,11 @@ import ExchangeRateGateway from "@/application/gateway/exchange-rate-gateway"; import ConvertCurrency from "@/application/use-case/convert-currency"; -import CurrencyNotFoundError from "@/domain/erros/currency-not-found"; import { ExchangeRateGatewayFake } from "@/infra/gateway/exchange-rate-gateway-fake"; describe('Currency Converter', () => { let exchangeRateGateway: ExchangeRateGateway; let convertCurrency: ConvertCurrency; + beforeEach(() => { exchangeRateGateway = new ExchangeRateGatewayFake(); convertCurrency = new ConvertCurrency(exchangeRateGateway); @@ -57,16 +57,7 @@ describe('Currency Converter', () => { to: 'BRL', amount: 1, } - const currencyNotFoundError = new CurrencyNotFoundError('INVALID'); - await expect(convertCurrency.execute(input)).rejects.toThrow(currencyNotFoundError); + await expect(convertCurrency.execute(input)).rejects.toThrow('Currency not found'); }); - it('should throw an error when the amount is a negative number', async () => { - const input = { - from: 'USD', - to: 'BRL', - amount: -1, - } - await expect(convertCurrency.execute(input)).rejects.toThrow('Invalid amount'); - }); }); \ No newline at end of file diff --git a/test/unity/currency-converter.test.ts b/test/unity/currency-converter.test.ts new file mode 100644 index 000000000..48bca248a --- /dev/null +++ b/test/unity/currency-converter.test.ts @@ -0,0 +1,25 @@ +import CurrencyConverter from "@/domain/currency-converter"; + +describe("Currency Converter", () => { + + it('should convert from smaller to bigger currency', () => { + const currencyConverter = new CurrencyConverter(1, 2); + expect(currencyConverter.convert(1)).toBe(2); + }); + + it('should convert from bigger to smaller currency', () => { + const currencyConverter = new CurrencyConverter(1, 0.5); + expect(currencyConverter.convert(2)).toBe(1); + }); + + it('should convert from equal currency', () => { + const currencyConverter = new CurrencyConverter(1, 1); + expect(currencyConverter.convert(1)).toBe(1); + }); + + it('should throw an error when the amount is a negative number', () => { + const currencyConverter = new CurrencyConverter(1, 5.20); + expect(() => currencyConverter.convert(-1)).toThrow("Invalid amount"); + }); + +}); \ No newline at end of file From d5af5ebbc22e96a41900e153adf5864634868bcc Mon Sep 17 00:00:00 2001 From: Leandro Reis Date: Sun, 2 Jun 2024 12:50:23 -0300 Subject: [PATCH 07/43] feat: Add validation for zero or negative rates in CurrencyConverter --- src/domain/currency-converter.ts | 3 +++ test/unity/currency-converter.test.ts | 5 +++++ 2 files changed, 8 insertions(+) diff --git a/src/domain/currency-converter.ts b/src/domain/currency-converter.ts index 4788fa82a..7a331b97c 100644 --- a/src/domain/currency-converter.ts +++ b/src/domain/currency-converter.ts @@ -5,6 +5,9 @@ export default class CurrencyConverter { private _rateTo: number; constructor(rateFrom: number, rateTo: number) { + if (rateFrom <= 0 || rateTo <= 0) { + throw new Error('Invalid rate'); + } this._rateFrom = rateFrom; this._rateTo = rateTo; } diff --git a/test/unity/currency-converter.test.ts b/test/unity/currency-converter.test.ts index 48bca248a..453f0424f 100644 --- a/test/unity/currency-converter.test.ts +++ b/test/unity/currency-converter.test.ts @@ -17,6 +17,11 @@ describe("Currency Converter", () => { expect(currencyConverter.convert(1)).toBe(1); }); + it('should throw an error when the rate is zero or negative', () => { + expect(() => new CurrencyConverter(0, 1)).toThrow("Invalid rate"); + expect(() => new CurrencyConverter(-1, 1)).toThrow("Invalid rate"); + }); + it('should throw an error when the amount is a negative number', () => { const currencyConverter = new CurrencyConverter(1, 5.20); expect(() => currencyConverter.convert(-1)).toThrow("Invalid amount"); From c789efd5c0198ab33d34ca4214640686d8d70181 Mon Sep 17 00:00:00 2001 From: Leandro Reis Date: Mon, 3 Jun 2024 09:56:49 -0300 Subject: [PATCH 08/43] refactor: Use custom error classes for throwing erros --- src/domain/currency-converter.ts | 5 +++-- src/domain/currency.ts | 3 ++- .../currency-not-found.error.ts} | 4 +++- src/domain/errors/invalid-params.error.ts | 6 ++++++ src/domain/errors/not-found.error.ts | 6 ++++++ src/infra/gateway/exchange-rate-gateway-fake.ts | 2 +- 6 files changed, 21 insertions(+), 5 deletions(-) rename src/domain/{erros/currency-not-found.ts => errors/currency-not-found.error.ts} (52%) create mode 100644 src/domain/errors/invalid-params.error.ts create mode 100644 src/domain/errors/not-found.error.ts diff --git a/src/domain/currency-converter.ts b/src/domain/currency-converter.ts index 7a331b97c..c75de64dc 100644 --- a/src/domain/currency-converter.ts +++ b/src/domain/currency-converter.ts @@ -1,4 +1,5 @@ import Currency from '@/domain/currency'; +import InvalidParamsError from './errors/invalid-params.error'; export default class CurrencyConverter { private _rateFrom: number; @@ -6,7 +7,7 @@ export default class CurrencyConverter { constructor(rateFrom: number, rateTo: number) { if (rateFrom <= 0 || rateTo <= 0) { - throw new Error('Invalid rate'); + throw new InvalidParamsError('Invalid rate'); } this._rateFrom = rateFrom; this._rateTo = rateTo; @@ -14,7 +15,7 @@ export default class CurrencyConverter { convert(amount: number): number { if (amount < 0) { - throw new Error('Invalid amount'); + throw new InvalidParamsError('Invalid amount'); } const amountCurrency = new Currency(amount) amountCurrency.multiply(this._rateTo / this._rateFrom); diff --git a/src/domain/currency.ts b/src/domain/currency.ts index cda35b057..043527d19 100644 --- a/src/domain/currency.ts +++ b/src/domain/currency.ts @@ -1,11 +1,12 @@ import currency from "currency.js"; +import InvalidParamsError from "./errors/invalid-params.error"; export default class Currency { private _value: currency; constructor(value: number) { if (typeof value !== 'number') { - throw new Error('Invalid value'); + throw new InvalidParamsError('Invalid value'); } this._value = currency(value); } diff --git a/src/domain/erros/currency-not-found.ts b/src/domain/errors/currency-not-found.error.ts similarity index 52% rename from src/domain/erros/currency-not-found.ts rename to src/domain/errors/currency-not-found.error.ts index c87d62a6b..438c61108 100644 --- a/src/domain/erros/currency-not-found.ts +++ b/src/domain/errors/currency-not-found.error.ts @@ -1,4 +1,6 @@ -export default class CurrencyNotFoundError extends Error { +import NotFoundError from "./not-found.error"; + +export default class CurrencyNotFoundError extends NotFoundError { constructor(currencyCode: string) { super(`Currency not found: ${currencyCode}`); this.name = 'CurrencyNotFound'; diff --git a/src/domain/errors/invalid-params.error.ts b/src/domain/errors/invalid-params.error.ts new file mode 100644 index 000000000..62fa7e644 --- /dev/null +++ b/src/domain/errors/invalid-params.error.ts @@ -0,0 +1,6 @@ +export default class InvalidParamsError extends Error { + constructor(message: string) { + super(message); + this.name = 'InvalidParamsError'; + } +} \ No newline at end of file diff --git a/src/domain/errors/not-found.error.ts b/src/domain/errors/not-found.error.ts new file mode 100644 index 000000000..3b82a7e9a --- /dev/null +++ b/src/domain/errors/not-found.error.ts @@ -0,0 +1,6 @@ +export default class NotFoundError extends Error { + constructor(message: string) { + super(message); + this.name = 'NotFoundError'; + } +} \ No newline at end of file diff --git a/src/infra/gateway/exchange-rate-gateway-fake.ts b/src/infra/gateway/exchange-rate-gateway-fake.ts index 6bd9aa28c..349c71b17 100644 --- a/src/infra/gateway/exchange-rate-gateway-fake.ts +++ b/src/infra/gateway/exchange-rate-gateway-fake.ts @@ -1,5 +1,5 @@ import ExchangeRateGateway, { CurrencyExchange } from "@/application/gateway/exchange-rate-gateway"; -import CurrencyNotFoundError from "@/domain/erros/currency-not-found"; +import CurrencyNotFoundError from "@/domain/errors/currency-not-found.error"; export class ExchangeRateGatewayFake implements ExchangeRateGateway { private _rates: { [key: string]: number } = { From 7dd7015daebf80625408df48bfe89dd275262f05 Mon Sep 17 00:00:00 2001 From: Leandro Reis Date: Thu, 6 Jun 2024 15:04:22 -0300 Subject: [PATCH 09/43] feat: implement CreateCurrencyRate use-case Include the GetCurrencyRateByCode use-case to support the test --- package.json | 1 + .../repository/currency-rate-repository.ts | 9 +++ .../service/equivalent-rate-calculator.ts | 18 +++++ .../use-case/create-currency-rate.ts | 30 ++++++++ .../use-case/get-currency-rate-by-code.ts | 30 ++++++++ src/domain/currency-rate.ts | 19 +++++ src/domain/errors/exchange-not-found.error.ts | 8 +++ .../currency-rate-repository-fake.ts | 70 +++++++++++++++++++ test/unity/create-currency-rate.test.ts | 62 ++++++++++++++++ test/unity/equivalent-rate-calculator.test.ts | 23 ++++++ yarn.lock | 5 ++ 11 files changed, 275 insertions(+) create mode 100644 src/application/repository/currency-rate-repository.ts create mode 100644 src/application/service/equivalent-rate-calculator.ts create mode 100644 src/application/use-case/create-currency-rate.ts create mode 100644 src/application/use-case/get-currency-rate-by-code.ts create mode 100644 src/domain/currency-rate.ts create mode 100644 src/domain/errors/exchange-not-found.error.ts create mode 100644 src/infra/repository/currency-rate-repository-fake.ts create mode 100644 test/unity/create-currency-rate.test.ts create mode 100644 test/unity/equivalent-rate-calculator.test.ts diff --git a/package.json b/package.json index 5dc2ea7d4..79c4859d9 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,7 @@ "test:watch": "jest --watch --detectOpenHandles" }, "dependencies": { + "crypto": "^1.0.1", "currency.js": "^2.0.4" }, "devDependencies": { diff --git a/src/application/repository/currency-rate-repository.ts b/src/application/repository/currency-rate-repository.ts new file mode 100644 index 000000000..b861a52d8 --- /dev/null +++ b/src/application/repository/currency-rate-repository.ts @@ -0,0 +1,9 @@ +import CurrencyRate from "@/domain/currency-rate"; + +export default interface CurrencyRateRepository { + findById(id: string): Promise; + findByCode(code: string): Promise; + add(currencyRate: CurrencyRate): Promise; + save(currencyRate: CurrencyRate): Promise; + delete(id: string): Promise; +} diff --git a/src/application/service/equivalent-rate-calculator.ts b/src/application/service/equivalent-rate-calculator.ts new file mode 100644 index 000000000..1215556cb --- /dev/null +++ b/src/application/service/equivalent-rate-calculator.ts @@ -0,0 +1,18 @@ +export class EquivalentRateCalculator { + + constructor(readonly equivalentCurrencyRate: number) { } + + calculateRate(baseAmount: number, equivalentCurrencyAmount: number): number { + if (baseAmount <= 0) { + throw new Error('Invalid attribute: baseAmount'); + } + if (equivalentCurrencyAmount <= 0) { + throw new Error('Invalid attribute: equivalentCurrencyAmount'); + } + const rate = baseAmount / equivalentCurrencyAmount * this.equivalentCurrencyRate; + return rate + } + +} + +export default EquivalentRateCalculator; diff --git a/src/application/use-case/create-currency-rate.ts b/src/application/use-case/create-currency-rate.ts new file mode 100644 index 000000000..fe50a7956 --- /dev/null +++ b/src/application/use-case/create-currency-rate.ts @@ -0,0 +1,30 @@ +import CurrencyRateRepository from "@/application/repository/currency-rate-repository"; +import EquivalentRateCalculator from "@/application/service/equivalent-rate-calculator"; +import CurrencyRate from "@/domain/currency-rate"; + +export default class CreateCurrencyRate { + constructor(private currencyRateRepository: CurrencyRateRepository) { } + + async execute(data: CreateCurrencyRateDto): Promise { + const equivalentCurrencyRate = await this.currencyRateRepository.findByCode(data.equivalentCurrencyCode); + const equivalentRateCalculator = new EquivalentRateCalculator(equivalentCurrencyRate.rate); + const rate = equivalentRateCalculator.calculateRate(data.baseAmount, data.equivalentCurrencyAmount) + const currencyRate = CurrencyRate.create(data.name, data.code, data.symbol, data.decimalPoints, rate); + await this.currencyRateRepository.add(currencyRate); + return { currencyRateId: currencyRate.id }; + } +} + +export type CreateCurrencyRateDto = { + name: string; + symbol: string; + code: string; + decimalPoints: number; + baseAmount: number; + equivalentCurrencyCode: string; + equivalentCurrencyAmount: number; +} + +export type Output = { + currencyRateId: string; +} diff --git a/src/application/use-case/get-currency-rate-by-code.ts b/src/application/use-case/get-currency-rate-by-code.ts new file mode 100644 index 000000000..aeb21ecc9 --- /dev/null +++ b/src/application/use-case/get-currency-rate-by-code.ts @@ -0,0 +1,30 @@ +import CurrencyRateRepository from "@/application/repository/currency-rate-repository"; + +export class GetCurrencyRateByCode { + + constructor(readonly currencyRateRepository: CurrencyRateRepository) { } + + async execute(code: string): Promise { + const currencyRate = await this.currencyRateRepository.findByCode(code); + return { + id: currencyRate.id, + name: currencyRate.name, + code: currencyRate.code, + symbol: currencyRate.symbol, + decimalPoints: currencyRate.decimalPoints, + rate: currencyRate.rate, + }; + } + +} + +export type Output = { + id: string; + name: string; + code: string; + symbol: string; + decimalPoints: number; + rate: number; +} + +export default GetCurrencyRateByCode; \ No newline at end of file diff --git a/src/domain/currency-rate.ts b/src/domain/currency-rate.ts new file mode 100644 index 000000000..1278219c1 --- /dev/null +++ b/src/domain/currency-rate.ts @@ -0,0 +1,19 @@ + +export default class CurrencyRate { + constructor( + readonly id: string, + readonly name: string, + readonly code: string, + readonly symbol: string, + readonly decimalPoints: number, + readonly rate: number, + ) { } + + static create(name: string, code: string, symbol: string, decimalPoints: number, rate: number): CurrencyRate { + if (rate <= 0) { + throw new Error('Invalid rate'); + } + const id = crypto.randomUUID(); + return new CurrencyRate(id, name, code, symbol, decimalPoints, rate); + } +} \ No newline at end of file diff --git a/src/domain/errors/exchange-not-found.error.ts b/src/domain/errors/exchange-not-found.error.ts new file mode 100644 index 000000000..a5708bea3 --- /dev/null +++ b/src/domain/errors/exchange-not-found.error.ts @@ -0,0 +1,8 @@ +import NotFoundError from "./not-found.error"; + +export default class ExchangeRateNotFoundError extends NotFoundError { + constructor(exchangeRateIdOrCode: string) { + super(`Exchange rate not found: ${exchangeRateIdOrCode}`); + this.name = 'ExchangeRateNotFound'; + } +} \ No newline at end of file diff --git a/src/infra/repository/currency-rate-repository-fake.ts b/src/infra/repository/currency-rate-repository-fake.ts new file mode 100644 index 000000000..fcd0a9617 --- /dev/null +++ b/src/infra/repository/currency-rate-repository-fake.ts @@ -0,0 +1,70 @@ +import CurrencyRateRepository from "@/application/repository/currency-rate-repository"; +import CurrencyRate from "@/domain/currency-rate"; +import CurrencyRateNotFoundError from "@/domain/errors/exchange-not-found.error"; + +export default class CurrencyRateRepositoryFake implements CurrencyRateRepository { + currencyRates: CurrencyRate[]; + + constructor() { + this.currencyRates = [ + { + id: '1', + name: 'US Dollar', + symbol: '$', + decimalPoints: 2, + code: 'USD', + rate: 1.00 + }, + { + id: '2', + name: 'Brazilian Real', + symbol: 'R$', + decimalPoints: 2, + code: 'BRL', + rate: 5.20 + } + ]; + } + + async findById(id: string) { + const currencyRate = this.currencyRates.find(currencyRate => currencyRate.id === id); + if (!currencyRate) { + throw new CurrencyRateNotFoundError(id); + } + return currencyRate; + } + + async findByCode(code: string) { + const currencyRate = this.currencyRates.find(currencyRate => currencyRate.code === code); + if (!currencyRate) { + throw new CurrencyRateNotFoundError(code); + } + return currencyRate; + } + + async add(currencyRate: CurrencyRate) { + const exists = await this.exists(currencyRate.code); + if (exists) { + throw new Error('Currency rate with code ' + currencyRate.code + ' already exists'); + } + this.currencyRates.push(currencyRate); + } + + async save(currencyRate: CurrencyRate) { + const index = this.currencyRates.findIndex(currencyRate => currencyRate.id === currencyRate.id); + if (index !== -1) { + this.currencyRates[index] = currencyRate; + } + } + + async delete(id: string) { + const index = this.currencyRates.findIndex(currencyRate => currencyRate.id === id); + if (index !== -1) { + this.currencyRates.splice(index, 1); + } + } + + async exists(code: string) { + return this.currencyRates.some(currencyRate => currencyRate.code === code); + } +} \ No newline at end of file diff --git a/test/unity/create-currency-rate.test.ts b/test/unity/create-currency-rate.test.ts new file mode 100644 index 000000000..7f7377093 --- /dev/null +++ b/test/unity/create-currency-rate.test.ts @@ -0,0 +1,62 @@ +import CreateCurrencyRate, { CreateCurrencyRateDto } from "@/application/use-case/create-currency-rate"; +import GetCurrencyRateByCode from "@/application/use-case/get-currency-rate-by-code"; +import CurrencyRateRepositoryFake from "@/infra/repository/currency-rate-repository-fake"; + +describe('create-currency-rate', () => { + it('should create a currency', async () => { + const currencyRateRepository = new CurrencyRateRepositoryFake(); + const createCurrency = new CreateCurrencyRate(currencyRateRepository); + const createCurrencyInput = { + name: "Grand Theft Auto Dollar", + symbol: "GTA$", + code: "GTA", + decimalPoints: 2, + baseAmount: 1250000.00, + equivalentCurrencyCode: "BRL", + equivalentCurrencyAmount: 83.50, + } + await createCurrency.execute(createCurrencyInput); + const getCurrencyRateByCode = new GetCurrencyRateByCode(currencyRateRepository); + const getCurrencyOutput = await getCurrencyRateByCode.execute(createCurrencyInput.code); + expect(getCurrencyOutput.name).toBe(createCurrencyInput.name); + expect(getCurrencyOutput.symbol).toBe(createCurrencyInput.symbol); + expect(getCurrencyOutput.code).toBe(createCurrencyInput.code); + expect(getCurrencyOutput.decimalPoints).toBe(createCurrencyInput.decimalPoints); + expect(getCurrencyOutput.rate).toBe(77844.31137724551); + }); + + it('should not create a currency with duplicated code', async () => { + const currencyRateRepository = new CurrencyRateRepositoryFake(); + const createCurrency = new CreateCurrencyRate(currencyRateRepository); + const createCurrencyInput: CreateCurrencyRateDto = { + name: 'Any Currency', + code: 'ANY', + symbol: 'A$', + decimalPoints: 2, + baseAmount: 1.00, + equivalentCurrencyCode: "USD", + equivalentCurrencyAmount: 1.00, + } + await createCurrency.execute(createCurrencyInput); + await expect(createCurrency.execute(createCurrencyInput)).rejects.toThrow('Currency rate with code ANY already exists'); + }); + + it('should not create a currency with invalid attribute', async () => { + const currencyRateRepository = new CurrencyRateRepositoryFake(); + const createCurrency = new CreateCurrencyRate(currencyRateRepository); + const createCurrencyInput: CreateCurrencyRateDto = { + name: 'Any Currency', + code: 'ANY', + symbol: 'A$', + decimalPoints: 2, + baseAmount: -1.00, + equivalentCurrencyCode: "USD", + equivalentCurrencyAmount: 1.00, + } + await expect(createCurrency.execute(createCurrencyInput)).rejects.toThrow('Invalid attribute: baseAmount'); + createCurrencyInput.baseAmount = 1.00; + createCurrencyInput.equivalentCurrencyAmount = -1.00; + await expect(createCurrency.execute(createCurrencyInput)).rejects.toThrow('Invalid attribute: equivalentCurrencyAmount'); + }); + +}); \ No newline at end of file diff --git a/test/unity/equivalent-rate-calculator.test.ts b/test/unity/equivalent-rate-calculator.test.ts new file mode 100644 index 000000000..8ae45f17c --- /dev/null +++ b/test/unity/equivalent-rate-calculator.test.ts @@ -0,0 +1,23 @@ +import { EquivalentRateCalculator } from "@/application/service/equivalent-rate-calculator"; +import CurrencyConverter from "@/domain/currency-converter"; + +describe('equivalent-rate-calculator', () => { + + it('should calculate the equivalent rate R$', async () => { + const equivalentRateCalculator = new EquivalentRateCalculator(1); + const equivalentRate = equivalentRateCalculator.calculateRate(10.40, 2.00); + const conversor = new CurrencyConverter(1, equivalentRate); + const convertedAmount = conversor.convert(2); + expect(convertedAmount).toBe(10.40); + }); + + it('should calculate the equivalent rate of GTA$', async () => { + const equivalentRateCalculator = new EquivalentRateCalculator(5.20); + const equivalentRate = equivalentRateCalculator.calculateRate(1250000.00, 83.50); + expect(equivalentRate).toBe(77844.31137724551); + const conversor = new CurrencyConverter(5.20, equivalentRate); + const convertedAmount = conversor.convert(83.50); + expect(convertedAmount).toBe(1250000.00); + }); + +}); \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index c4f9bff79..cb71a89e4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -984,6 +984,11 @@ cross-spawn@^7.0.3: shebang-command "^2.0.0" which "^2.0.1" +crypto@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/crypto/-/crypto-1.0.1.tgz#2af1b7cad8175d24c8a1b0778255794a21803037" + integrity sha512-VxBKmeNcqQdiUQUW2Tzq0t377b54N2bMtXO/qiLa+6eRRmmC4qT3D4OnTGoT/U6O9aklQ/jTwbOtRMTTY8G0Ig== + currency.js@^2.0.4: version "2.0.4" resolved "https://registry.yarnpkg.com/currency.js/-/currency.js-2.0.4.tgz#a8a4d69be3b2e509bf67a560c78220bc04809cf1" From fcdf8253706419597b8c5e48e139131483e97937 Mon Sep 17 00:00:00 2001 From: Leandro Reis Date: Thu, 6 Jun 2024 15:27:00 -0300 Subject: [PATCH 10/43] refactor: Move CurrencyConverter to application/service directory --- src/{domain => application/service}/currency-converter.ts | 2 +- src/application/use-case/convert-currency.ts | 2 +- test/unity/currency-converter.test.ts | 2 +- test/unity/equivalent-rate-calculator.test.ts | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) rename src/{domain => application/service}/currency-converter.ts (89%) diff --git a/src/domain/currency-converter.ts b/src/application/service/currency-converter.ts similarity index 89% rename from src/domain/currency-converter.ts rename to src/application/service/currency-converter.ts index c75de64dc..1e5ff6dbc 100644 --- a/src/domain/currency-converter.ts +++ b/src/application/service/currency-converter.ts @@ -1,5 +1,5 @@ import Currency from '@/domain/currency'; -import InvalidParamsError from './errors/invalid-params.error'; +import InvalidParamsError from '@/domain/errors/invalid-params.error'; export default class CurrencyConverter { private _rateFrom: number; diff --git a/src/application/use-case/convert-currency.ts b/src/application/use-case/convert-currency.ts index b715f3fb6..dc53fa12e 100644 --- a/src/application/use-case/convert-currency.ts +++ b/src/application/use-case/convert-currency.ts @@ -1,5 +1,5 @@ import ExchangeRateGateway from "@/application/gateway/exchange-rate-gateway"; -import CurrencyConverter from "@/domain/currency-converter"; +import CurrencyConverter from "@/application/service/currency-converter"; export default class ConvertCurrency { constructor(readonly exchangeRateGateway: ExchangeRateGateway) { diff --git a/test/unity/currency-converter.test.ts b/test/unity/currency-converter.test.ts index 453f0424f..e671c2260 100644 --- a/test/unity/currency-converter.test.ts +++ b/test/unity/currency-converter.test.ts @@ -1,4 +1,4 @@ -import CurrencyConverter from "@/domain/currency-converter"; +import CurrencyConverter from "@/application/service/currency-converter"; describe("Currency Converter", () => { diff --git a/test/unity/equivalent-rate-calculator.test.ts b/test/unity/equivalent-rate-calculator.test.ts index 8ae45f17c..badd044c3 100644 --- a/test/unity/equivalent-rate-calculator.test.ts +++ b/test/unity/equivalent-rate-calculator.test.ts @@ -1,5 +1,5 @@ +import CurrencyConverter from "@/application/service/currency-converter"; import { EquivalentRateCalculator } from "@/application/service/equivalent-rate-calculator"; -import CurrencyConverter from "@/domain/currency-converter"; describe('equivalent-rate-calculator', () => { From 7d39804d0a98cb6053772f58fcdf61eab5f93c9a Mon Sep 17 00:00:00 2001 From: Leandro Reis Date: Thu, 6 Jun 2024 15:28:13 -0300 Subject: [PATCH 11/43] refactor: rename exchange-not-found to currency-rate-not-found --- src/domain/errors/currency-rate-not-found.error.ts | 8 ++++++++ src/domain/errors/exchange-not-found.error.ts | 8 -------- src/infra/repository/currency-rate-repository-fake.ts | 2 +- 3 files changed, 9 insertions(+), 9 deletions(-) create mode 100644 src/domain/errors/currency-rate-not-found.error.ts delete mode 100644 src/domain/errors/exchange-not-found.error.ts diff --git a/src/domain/errors/currency-rate-not-found.error.ts b/src/domain/errors/currency-rate-not-found.error.ts new file mode 100644 index 000000000..ae7b5a9d4 --- /dev/null +++ b/src/domain/errors/currency-rate-not-found.error.ts @@ -0,0 +1,8 @@ +import NotFoundError from "./not-found.error"; + +export default class CurrencyRateNotFoundError extends NotFoundError { + constructor(currencyRateIdOrCode: string) { + super(`Currency rate not found: ${currencyRateIdOrCode}`); + this.name = 'CurrencyRateNotFoundError'; + } +} \ No newline at end of file diff --git a/src/domain/errors/exchange-not-found.error.ts b/src/domain/errors/exchange-not-found.error.ts deleted file mode 100644 index a5708bea3..000000000 --- a/src/domain/errors/exchange-not-found.error.ts +++ /dev/null @@ -1,8 +0,0 @@ -import NotFoundError from "./not-found.error"; - -export default class ExchangeRateNotFoundError extends NotFoundError { - constructor(exchangeRateIdOrCode: string) { - super(`Exchange rate not found: ${exchangeRateIdOrCode}`); - this.name = 'ExchangeRateNotFound'; - } -} \ No newline at end of file diff --git a/src/infra/repository/currency-rate-repository-fake.ts b/src/infra/repository/currency-rate-repository-fake.ts index fcd0a9617..738189f0f 100644 --- a/src/infra/repository/currency-rate-repository-fake.ts +++ b/src/infra/repository/currency-rate-repository-fake.ts @@ -1,6 +1,6 @@ import CurrencyRateRepository from "@/application/repository/currency-rate-repository"; import CurrencyRate from "@/domain/currency-rate"; -import CurrencyRateNotFoundError from "@/domain/errors/exchange-not-found.error"; +import CurrencyRateNotFoundError from "@/domain/errors/currency-rate-not-found.error"; export default class CurrencyRateRepositoryFake implements CurrencyRateRepository { currencyRates: CurrencyRate[]; From 81a02a2f776833609cdf0794b732febb89192526 Mon Sep 17 00:00:00 2001 From: Leandro Reis Date: Thu, 6 Jun 2024 15:29:12 -0300 Subject: [PATCH 12/43] feat: implement DeleteCurrencyRateByCode use case --- .../use-case/delete-currency-rate-by-code.ts | 13 ++++++++ test/unity/delete-currency-rate.test.ts | 33 +++++++++++++++++++ 2 files changed, 46 insertions(+) create mode 100644 src/application/use-case/delete-currency-rate-by-code.ts create mode 100644 test/unity/delete-currency-rate.test.ts diff --git a/src/application/use-case/delete-currency-rate-by-code.ts b/src/application/use-case/delete-currency-rate-by-code.ts new file mode 100644 index 000000000..25618c6ee --- /dev/null +++ b/src/application/use-case/delete-currency-rate-by-code.ts @@ -0,0 +1,13 @@ +import CurrencyRateRepository from "../repository/currency-rate-repository"; + +export class DeleteCurrencyRateByCode { + constructor(private readonly currencyRateRepository: CurrencyRateRepository) { } + + async execute(code: string): Promise { + const currencyRate = await this.currencyRateRepository.findByCode(code); + if (!currencyRate) { + throw new Error('Currency rate not found'); + } + await this.currencyRateRepository.delete(currencyRate.id); + } +} \ No newline at end of file diff --git a/test/unity/delete-currency-rate.test.ts b/test/unity/delete-currency-rate.test.ts new file mode 100644 index 000000000..208177eba --- /dev/null +++ b/test/unity/delete-currency-rate.test.ts @@ -0,0 +1,33 @@ +import CreateCurrencyRate from "@/application/use-case/create-currency-rate"; +import { DeleteCurrencyRateByCode } from "@/application/use-case/delete-currency-rate-by-code"; +import GetCurrencyRateByCode from "@/application/use-case/get-currency-rate-by-code"; +import CurrencyRateRepositoryFake from "@/infra/repository/currency-rate-repository-fake"; + +describe('delete-currency-rate', () => { + + it('should delete a currency rate by code', async () => { + const currencyRateRepository = new CurrencyRateRepositoryFake(); + const createCurrency = new CreateCurrencyRate(currencyRateRepository); + const createCurrencyInput = { + name: "Any Currency", + symbol: "AC$", + code: "AYC", + decimalPoints: 2, + baseAmount: 1.00, + equivalentCurrencyCode: "USD", + equivalentCurrencyAmount: 1.00, + } + await createCurrency.execute(createCurrencyInput); + const deleteCurrencyRate = new DeleteCurrencyRateByCode(currencyRateRepository); + await deleteCurrencyRate.execute(createCurrencyInput.code); + const getCurrencyRateByCode = new GetCurrencyRateByCode(currencyRateRepository); + await expect(getCurrencyRateByCode.execute(createCurrencyInput.code)).rejects.toThrow('Currency rate not found'); + }); + + it('should throw an error when trying to delete a currency rate that does not exist', async () => { + const currencyRateRepository = new CurrencyRateRepositoryFake(); + const deleteCurrencyRate = new DeleteCurrencyRateByCode(currencyRateRepository); + await expect(deleteCurrencyRate.execute('ABC')).rejects.toThrow('Currency rate not found'); + }); + +}); \ No newline at end of file From c9c8a820b382ec1b8982e22d137ec709ebc19117 Mon Sep 17 00:00:00 2001 From: Leandro Reis Date: Thu, 6 Jun 2024 15:41:53 -0300 Subject: [PATCH 13/43] refactor: update ConvertCurrency to use CurrencyRateRepository instead of ExchangeRateGateway --- .../gateway/exchange-rate-gateway.ts | 8 -------- src/application/use-case/convert-currency.ts | 8 ++++---- .../gateway/exchange-rate-gateway-fake.ts | 19 ------------------- .../currency-rate-repository-fake.ts | 8 ++++++++ test/unity/convert-currency.test.ts | 11 +++++------ 5 files changed, 17 insertions(+), 37 deletions(-) delete mode 100644 src/application/gateway/exchange-rate-gateway.ts delete mode 100644 src/infra/gateway/exchange-rate-gateway-fake.ts diff --git a/src/application/gateway/exchange-rate-gateway.ts b/src/application/gateway/exchange-rate-gateway.ts deleted file mode 100644 index a2542acf5..000000000 --- a/src/application/gateway/exchange-rate-gateway.ts +++ /dev/null @@ -1,8 +0,0 @@ -export default interface ExchangeRateGateway { - getExchangeRate(exchangeCode: string): Promise; -} - -export type CurrencyExchange = { - code: string; - rate: number; -} \ No newline at end of file diff --git a/src/application/use-case/convert-currency.ts b/src/application/use-case/convert-currency.ts index dc53fa12e..76d925179 100644 --- a/src/application/use-case/convert-currency.ts +++ b/src/application/use-case/convert-currency.ts @@ -1,13 +1,13 @@ -import ExchangeRateGateway from "@/application/gateway/exchange-rate-gateway"; import CurrencyConverter from "@/application/service/currency-converter"; +import CurrencyRateRepository from "../repository/currency-rate-repository"; export default class ConvertCurrency { - constructor(readonly exchangeRateGateway: ExchangeRateGateway) { + constructor(readonly currencyRateRepository: CurrencyRateRepository) { } async execute(input: Input): Promise { - const exchangeFrom = await this.exchangeRateGateway.getExchangeRate(input.from); - const exchangeTo = await this.exchangeRateGateway.getExchangeRate(input.to); + const exchangeFrom = await this.currencyRateRepository.findByCode(input.from); + const exchangeTo = await this.currencyRateRepository.findByCode(input.to); const currencyConverter = new CurrencyConverter(exchangeFrom.rate, exchangeTo.rate); const convertedAmount = currencyConverter.convert(input.amount); return { value: convertedAmount } diff --git a/src/infra/gateway/exchange-rate-gateway-fake.ts b/src/infra/gateway/exchange-rate-gateway-fake.ts deleted file mode 100644 index 349c71b17..000000000 --- a/src/infra/gateway/exchange-rate-gateway-fake.ts +++ /dev/null @@ -1,19 +0,0 @@ -import ExchangeRateGateway, { CurrencyExchange } from "@/application/gateway/exchange-rate-gateway"; -import CurrencyNotFoundError from "@/domain/errors/currency-not-found.error"; - -export class ExchangeRateGatewayFake implements ExchangeRateGateway { - private _rates: { [key: string]: number } = { - USD: 1, - BRL: 5.20, - EUR: 0.92, - }; - async getExchangeRate(exchangeCode: string): Promise { - if (this._rates[exchangeCode]) { - return { - code: exchangeCode, - rate: this._rates[exchangeCode], - } - } - throw new CurrencyNotFoundError(exchangeCode); - } -} \ No newline at end of file diff --git a/src/infra/repository/currency-rate-repository-fake.ts b/src/infra/repository/currency-rate-repository-fake.ts index 738189f0f..25875a69e 100644 --- a/src/infra/repository/currency-rate-repository-fake.ts +++ b/src/infra/repository/currency-rate-repository-fake.ts @@ -22,6 +22,14 @@ export default class CurrencyRateRepositoryFake implements CurrencyRateRepositor decimalPoints: 2, code: 'BRL', rate: 5.20 + }, + { + id: '3', + name: 'Euro', + symbol: '€', + decimalPoints: 2, + code: 'EUR', + rate: 0.92 } ]; } diff --git a/test/unity/convert-currency.test.ts b/test/unity/convert-currency.test.ts index 5082517b8..89a344e4d 100644 --- a/test/unity/convert-currency.test.ts +++ b/test/unity/convert-currency.test.ts @@ -1,14 +1,13 @@ -import ExchangeRateGateway from "@/application/gateway/exchange-rate-gateway"; import ConvertCurrency from "@/application/use-case/convert-currency"; -import { ExchangeRateGatewayFake } from "@/infra/gateway/exchange-rate-gateway-fake"; +import CurrencyRateRepositoryFake from "@/infra/repository/currency-rate-repository-fake"; describe('Currency Converter', () => { - let exchangeRateGateway: ExchangeRateGateway; + let currencyRateRepository: CurrencyRateRepositoryFake; let convertCurrency: ConvertCurrency; beforeEach(() => { - exchangeRateGateway = new ExchangeRateGatewayFake(); - convertCurrency = new ConvertCurrency(exchangeRateGateway); + currencyRateRepository = new CurrencyRateRepositoryFake(); + convertCurrency = new ConvertCurrency(currencyRateRepository); }); it('should convert USD to BRL', async () => { @@ -57,7 +56,7 @@ describe('Currency Converter', () => { to: 'BRL', amount: 1, } - await expect(convertCurrency.execute(input)).rejects.toThrow('Currency not found'); + await expect(convertCurrency.execute(input)).rejects.toThrow('Currency rate not found'); }); }); \ No newline at end of file From 0ae1afb7119935ace03a9a0f0e9c4b1c0a369995 Mon Sep 17 00:00:00 2001 From: Leandro Reis Date: Thu, 6 Jun 2024 16:16:08 -0300 Subject: [PATCH 14/43] refactor: ensure that it is not possible to delete the backing currency --- src/application/use-case/delete-currency-rate-by-code.ts | 3 +++ test/unity/delete-currency-rate.test.ts | 6 ++++++ 2 files changed, 9 insertions(+) diff --git a/src/application/use-case/delete-currency-rate-by-code.ts b/src/application/use-case/delete-currency-rate-by-code.ts index 25618c6ee..ab38d8d7d 100644 --- a/src/application/use-case/delete-currency-rate-by-code.ts +++ b/src/application/use-case/delete-currency-rate-by-code.ts @@ -4,6 +4,9 @@ export class DeleteCurrencyRateByCode { constructor(private readonly currencyRateRepository: CurrencyRateRepository) { } async execute(code: string): Promise { + if (code === 'USD') { + throw new Error('Cannot delete USD currency rate'); + } const currencyRate = await this.currencyRateRepository.findByCode(code); if (!currencyRate) { throw new Error('Currency rate not found'); diff --git a/test/unity/delete-currency-rate.test.ts b/test/unity/delete-currency-rate.test.ts index 208177eba..5004d28ed 100644 --- a/test/unity/delete-currency-rate.test.ts +++ b/test/unity/delete-currency-rate.test.ts @@ -30,4 +30,10 @@ describe('delete-currency-rate', () => { await expect(deleteCurrencyRate.execute('ABC')).rejects.toThrow('Currency rate not found'); }); + it('should throw an error when trying to delete USD currency rate', async () => { + const currencyRateRepository = new CurrencyRateRepositoryFake(); + const deleteCurrencyRate = new DeleteCurrencyRateByCode(currencyRateRepository); + await expect(deleteCurrencyRate.execute('USD')).rejects.toThrow('Cannot delete USD currency rate'); + }); + }); \ No newline at end of file From 674940f6cf1c4098d691a132a2740ea91310beab Mon Sep 17 00:00:00 2001 From: Leandro Reis Date: Thu, 6 Jun 2024 16:17:45 -0300 Subject: [PATCH 15/43] refactor: remove not used functions --- .../repository/currency-rate-repository.ts | 2 -- .../repository/currency-rate-repository-fake.ts | 15 --------------- 2 files changed, 17 deletions(-) diff --git a/src/application/repository/currency-rate-repository.ts b/src/application/repository/currency-rate-repository.ts index b861a52d8..604c76c8f 100644 --- a/src/application/repository/currency-rate-repository.ts +++ b/src/application/repository/currency-rate-repository.ts @@ -1,9 +1,7 @@ import CurrencyRate from "@/domain/currency-rate"; export default interface CurrencyRateRepository { - findById(id: string): Promise; findByCode(code: string): Promise; add(currencyRate: CurrencyRate): Promise; - save(currencyRate: CurrencyRate): Promise; delete(id: string): Promise; } diff --git a/src/infra/repository/currency-rate-repository-fake.ts b/src/infra/repository/currency-rate-repository-fake.ts index 25875a69e..696933531 100644 --- a/src/infra/repository/currency-rate-repository-fake.ts +++ b/src/infra/repository/currency-rate-repository-fake.ts @@ -34,14 +34,6 @@ export default class CurrencyRateRepositoryFake implements CurrencyRateRepositor ]; } - async findById(id: string) { - const currencyRate = this.currencyRates.find(currencyRate => currencyRate.id === id); - if (!currencyRate) { - throw new CurrencyRateNotFoundError(id); - } - return currencyRate; - } - async findByCode(code: string) { const currencyRate = this.currencyRates.find(currencyRate => currencyRate.code === code); if (!currencyRate) { @@ -58,13 +50,6 @@ export default class CurrencyRateRepositoryFake implements CurrencyRateRepositor this.currencyRates.push(currencyRate); } - async save(currencyRate: CurrencyRate) { - const index = this.currencyRates.findIndex(currencyRate => currencyRate.id === currencyRate.id); - if (index !== -1) { - this.currencyRates[index] = currencyRate; - } - } - async delete(id: string) { const index = this.currencyRates.findIndex(currencyRate => currencyRate.id === id); if (index !== -1) { From a1b69e3c6c35a775e13b00e5cff159c56b299613 Mon Sep 17 00:00:00 2001 From: Leandro Reis Date: Thu, 6 Jun 2024 18:09:35 -0300 Subject: [PATCH 16/43] refactor: move conversion responsibility to the CurrencyRate entity --- src/application/service/currency-converter.ts | 24 --------------- src/application/use-case/convert-currency.ts | 4 +-- src/domain/currency-rate.ts | 12 ++++++++ src/domain/currency.ts | 23 -------------- src/domain/errors/currency-not-found.error.ts | 8 ----- .../currency-rate-repository-fake.ts | 27 ++--------------- test/unity/convert-currency.test.ts | 9 ++++++ test/unity/currency-converter.test.ts | 30 ------------------- test/unity/currenty-rate.test.ts | 14 +++++++++ test/unity/equivalent-rate-calculator.test.ts | 8 +---- 10 files changed, 40 insertions(+), 119 deletions(-) delete mode 100644 src/application/service/currency-converter.ts delete mode 100644 src/domain/currency.ts delete mode 100644 src/domain/errors/currency-not-found.error.ts delete mode 100644 test/unity/currency-converter.test.ts create mode 100644 test/unity/currenty-rate.test.ts diff --git a/src/application/service/currency-converter.ts b/src/application/service/currency-converter.ts deleted file mode 100644 index 1e5ff6dbc..000000000 --- a/src/application/service/currency-converter.ts +++ /dev/null @@ -1,24 +0,0 @@ -import Currency from '@/domain/currency'; -import InvalidParamsError from '@/domain/errors/invalid-params.error'; - -export default class CurrencyConverter { - private _rateFrom: number; - private _rateTo: number; - - constructor(rateFrom: number, rateTo: number) { - if (rateFrom <= 0 || rateTo <= 0) { - throw new InvalidParamsError('Invalid rate'); - } - this._rateFrom = rateFrom; - this._rateTo = rateTo; - } - - convert(amount: number): number { - if (amount < 0) { - throw new InvalidParamsError('Invalid amount'); - } - const amountCurrency = new Currency(amount) - amountCurrency.multiply(this._rateTo / this._rateFrom); - return amountCurrency.value; - } -} \ No newline at end of file diff --git a/src/application/use-case/convert-currency.ts b/src/application/use-case/convert-currency.ts index 76d925179..a4d9fae4e 100644 --- a/src/application/use-case/convert-currency.ts +++ b/src/application/use-case/convert-currency.ts @@ -1,4 +1,3 @@ -import CurrencyConverter from "@/application/service/currency-converter"; import CurrencyRateRepository from "../repository/currency-rate-repository"; export default class ConvertCurrency { @@ -8,8 +7,7 @@ export default class ConvertCurrency { async execute(input: Input): Promise { const exchangeFrom = await this.currencyRateRepository.findByCode(input.from); const exchangeTo = await this.currencyRateRepository.findByCode(input.to); - const currencyConverter = new CurrencyConverter(exchangeFrom.rate, exchangeTo.rate); - const convertedAmount = currencyConverter.convert(input.amount); + const convertedAmount = exchangeFrom.convertTo(exchangeTo, input.amount); return { value: convertedAmount } } } diff --git a/src/domain/currency-rate.ts b/src/domain/currency-rate.ts index 1278219c1..2a486567a 100644 --- a/src/domain/currency-rate.ts +++ b/src/domain/currency-rate.ts @@ -1,5 +1,7 @@ +import currency from "currency.js"; export default class CurrencyRate { + constructor( readonly id: string, readonly name: string, @@ -16,4 +18,14 @@ export default class CurrencyRate { const id = crypto.randomUUID(); return new CurrencyRate(id, name, code, symbol, decimalPoints, rate); } + + convertTo(currencyRate: CurrencyRate, amount: number): number { + if (amount < 0) { + throw new Error('Invalid amount'); + } + const convertedAmount = amount * currencyRate.rate / this.rate; + const amountCurrency = currency(convertedAmount, { precision: currencyRate.decimalPoints }); + return amountCurrency.value; + } + } \ No newline at end of file diff --git a/src/domain/currency.ts b/src/domain/currency.ts deleted file mode 100644 index 043527d19..000000000 --- a/src/domain/currency.ts +++ /dev/null @@ -1,23 +0,0 @@ -import currency from "currency.js"; -import InvalidParamsError from "./errors/invalid-params.error"; - -export default class Currency { - private _value: currency; - - constructor(value: number) { - if (typeof value !== 'number') { - throw new InvalidParamsError('Invalid value'); - } - this._value = currency(value); - } - - get value(): number { - return this._value.value; - } - - multiply(value: number): void { - this._value = this._value.multiply(value); - } -} - -// currency.js documentation: https://currency.js.org/ \ No newline at end of file diff --git a/src/domain/errors/currency-not-found.error.ts b/src/domain/errors/currency-not-found.error.ts deleted file mode 100644 index 438c61108..000000000 --- a/src/domain/errors/currency-not-found.error.ts +++ /dev/null @@ -1,8 +0,0 @@ -import NotFoundError from "./not-found.error"; - -export default class CurrencyNotFoundError extends NotFoundError { - constructor(currencyCode: string) { - super(`Currency not found: ${currencyCode}`); - this.name = 'CurrencyNotFound'; - } -} \ No newline at end of file diff --git a/src/infra/repository/currency-rate-repository-fake.ts b/src/infra/repository/currency-rate-repository-fake.ts index 696933531..05d67e139 100644 --- a/src/infra/repository/currency-rate-repository-fake.ts +++ b/src/infra/repository/currency-rate-repository-fake.ts @@ -7,30 +7,9 @@ export default class CurrencyRateRepositoryFake implements CurrencyRateRepositor constructor() { this.currencyRates = [ - { - id: '1', - name: 'US Dollar', - symbol: '$', - decimalPoints: 2, - code: 'USD', - rate: 1.00 - }, - { - id: '2', - name: 'Brazilian Real', - symbol: 'R$', - decimalPoints: 2, - code: 'BRL', - rate: 5.20 - }, - { - id: '3', - name: 'Euro', - symbol: '€', - decimalPoints: 2, - code: 'EUR', - rate: 0.92 - } + new CurrencyRate('1', 'US Dollar', 'USD', '$', 2, 1), + new CurrencyRate('2', 'Brazilian Real', 'BRL', 'R$', 2, 5.20), + new CurrencyRate('3', 'Euro', 'EUR', '€', 2, 0.92) ]; } diff --git a/test/unity/convert-currency.test.ts b/test/unity/convert-currency.test.ts index 89a344e4d..9cf615b10 100644 --- a/test/unity/convert-currency.test.ts +++ b/test/unity/convert-currency.test.ts @@ -59,4 +59,13 @@ describe('Currency Converter', () => { await expect(convertCurrency.execute(input)).rejects.toThrow('Currency rate not found'); }); + it('should throw an error when the amount is invalid', async () => { + const input = { + from: 'USD', + to: 'BRL', + amount: -1, + } + await expect(convertCurrency.execute(input)).rejects.toThrow('Invalid amount'); + }); + }); \ No newline at end of file diff --git a/test/unity/currency-converter.test.ts b/test/unity/currency-converter.test.ts deleted file mode 100644 index e671c2260..000000000 --- a/test/unity/currency-converter.test.ts +++ /dev/null @@ -1,30 +0,0 @@ -import CurrencyConverter from "@/application/service/currency-converter"; - -describe("Currency Converter", () => { - - it('should convert from smaller to bigger currency', () => { - const currencyConverter = new CurrencyConverter(1, 2); - expect(currencyConverter.convert(1)).toBe(2); - }); - - it('should convert from bigger to smaller currency', () => { - const currencyConverter = new CurrencyConverter(1, 0.5); - expect(currencyConverter.convert(2)).toBe(1); - }); - - it('should convert from equal currency', () => { - const currencyConverter = new CurrencyConverter(1, 1); - expect(currencyConverter.convert(1)).toBe(1); - }); - - it('should throw an error when the rate is zero or negative', () => { - expect(() => new CurrencyConverter(0, 1)).toThrow("Invalid rate"); - expect(() => new CurrencyConverter(-1, 1)).toThrow("Invalid rate"); - }); - - it('should throw an error when the amount is a negative number', () => { - const currencyConverter = new CurrencyConverter(1, 5.20); - expect(() => currencyConverter.convert(-1)).toThrow("Invalid amount"); - }); - -}); \ No newline at end of file diff --git a/test/unity/currenty-rate.test.ts b/test/unity/currenty-rate.test.ts new file mode 100644 index 000000000..204482221 --- /dev/null +++ b/test/unity/currenty-rate.test.ts @@ -0,0 +1,14 @@ +import CurrencyRate from "@/domain/currency-rate"; + +describe('Currency Rate', () => { + + it('should create a currency rate', () => { + const currencyRate = CurrencyRate.create('US Dollar', 'USD', '$', 2, 1); + expect(currencyRate).toBeDefined(); + }); + + it('should throw an error when the rate is invalid', () => { + expect(() => CurrencyRate.create('US Dollar', 'USD', '$', 2, 0)).toThrow('Invalid rate'); + }); + +}); \ No newline at end of file diff --git a/test/unity/equivalent-rate-calculator.test.ts b/test/unity/equivalent-rate-calculator.test.ts index badd044c3..1a13bcbe7 100644 --- a/test/unity/equivalent-rate-calculator.test.ts +++ b/test/unity/equivalent-rate-calculator.test.ts @@ -1,4 +1,3 @@ -import CurrencyConverter from "@/application/service/currency-converter"; import { EquivalentRateCalculator } from "@/application/service/equivalent-rate-calculator"; describe('equivalent-rate-calculator', () => { @@ -6,18 +5,13 @@ describe('equivalent-rate-calculator', () => { it('should calculate the equivalent rate R$', async () => { const equivalentRateCalculator = new EquivalentRateCalculator(1); const equivalentRate = equivalentRateCalculator.calculateRate(10.40, 2.00); - const conversor = new CurrencyConverter(1, equivalentRate); - const convertedAmount = conversor.convert(2); - expect(convertedAmount).toBe(10.40); + expect(equivalentRate).toBe(5.20); }); it('should calculate the equivalent rate of GTA$', async () => { const equivalentRateCalculator = new EquivalentRateCalculator(5.20); const equivalentRate = equivalentRateCalculator.calculateRate(1250000.00, 83.50); expect(equivalentRate).toBe(77844.31137724551); - const conversor = new CurrencyConverter(5.20, equivalentRate); - const convertedAmount = conversor.convert(83.50); - expect(convertedAmount).toBe(1250000.00); }); }); \ No newline at end of file From 8fc401dda992d252ce9364d3ef72a63dd88918be Mon Sep 17 00:00:00 2001 From: Leandro Reis Date: Thu, 6 Jun 2024 18:56:09 -0300 Subject: [PATCH 17/43] refactor: Improve CurrencyRate entity to validate constructor attributes using value objects --- src/domain/currency-rate.ts | 52 +++++++++++++++++++---- src/domain/value-object/code.ts | 16 +++++++ src/domain/value-object/decimal-points.ts | 16 +++++++ src/domain/value-object/name.ts | 16 +++++++ src/domain/value-object/rate.ts | 16 +++++++ src/domain/value-object/symbol.ts | 16 +++++++ test/unity/currenty-rate.test.ts | 32 ++++++++++++++ 7 files changed, 155 insertions(+), 9 deletions(-) create mode 100644 src/domain/value-object/code.ts create mode 100644 src/domain/value-object/decimal-points.ts create mode 100644 src/domain/value-object/name.ts create mode 100644 src/domain/value-object/rate.ts create mode 100644 src/domain/value-object/symbol.ts diff --git a/src/domain/currency-rate.ts b/src/domain/currency-rate.ts index 2a486567a..eff0bb342 100644 --- a/src/domain/currency-rate.ts +++ b/src/domain/currency-rate.ts @@ -1,24 +1,58 @@ import currency from "currency.js"; +import Code from "@/domain/value-object/code"; +import DecimalPoints from "@/domain/value-object/decimal-points"; +import Name from "@/domain/value-object/name"; +import Rate from "@/domain/value-object/rate"; +import Symbol from "@/domain/value-object/symbol"; + export default class CurrencyRate { + private _name: Name; + private _code: Code; + private _symbol: Symbol; + private _decimalPoints: DecimalPoints; + private _rate: Rate; constructor( readonly id: string, - readonly name: string, - readonly code: string, - readonly symbol: string, - readonly decimalPoints: number, - readonly rate: number, - ) { } + name: string, + code: string, + symbol: string, + decimalPoints: number, + rate: number, + ) { + this._name = new Name(name); + this._code = new Code(code); + this._symbol = new Symbol(symbol); + this._decimalPoints = new DecimalPoints(decimalPoints); + this._rate = new Rate(rate); + } static create(name: string, code: string, symbol: string, decimalPoints: number, rate: number): CurrencyRate { - if (rate <= 0) { - throw new Error('Invalid rate'); - } const id = crypto.randomUUID(); return new CurrencyRate(id, name, code, symbol, decimalPoints, rate); } + get name(): string { + return this._name.value; + } + + get code(): string { + return this._code.value; + } + + get symbol(): string { + return this._symbol.value; + } + + get decimalPoints(): number { + return this._decimalPoints.value; + } + + get rate(): number { + return this._rate.value; + } + convertTo(currencyRate: CurrencyRate, amount: number): number { if (amount < 0) { throw new Error('Invalid amount'); diff --git a/src/domain/value-object/code.ts b/src/domain/value-object/code.ts new file mode 100644 index 000000000..e3664fb09 --- /dev/null +++ b/src/domain/value-object/code.ts @@ -0,0 +1,16 @@ +export class Code { + private _value: string; + + constructor(value: string) { + if (value.length < 3 || value.length > 4) { + throw new Error('Invalid code'); + } + this._value = value; + } + + get value() { + return this._value; + } +} + +export default Code; \ No newline at end of file diff --git a/src/domain/value-object/decimal-points.ts b/src/domain/value-object/decimal-points.ts new file mode 100644 index 000000000..c922012ba --- /dev/null +++ b/src/domain/value-object/decimal-points.ts @@ -0,0 +1,16 @@ +export class DecimalPoints { + private _value: number; + + constructor(value: number) { + if (value < 0 || value > 10) { + throw new Error('Invalid decimal point'); + } + this._value = value; + } + + get value() { + return this._value; + } +} + +export default DecimalPoints; \ No newline at end of file diff --git a/src/domain/value-object/name.ts b/src/domain/value-object/name.ts new file mode 100644 index 000000000..29b742525 --- /dev/null +++ b/src/domain/value-object/name.ts @@ -0,0 +1,16 @@ +export class Name { + private _value: string; + + constructor(value: string) { + if (value.length < 3 || value.length > 100) { + throw new Error('Invalid name'); + } + this._value = value; + } + + get value() { + return this._value; + } +} + +export default Name; \ No newline at end of file diff --git a/src/domain/value-object/rate.ts b/src/domain/value-object/rate.ts new file mode 100644 index 000000000..7aefcd596 --- /dev/null +++ b/src/domain/value-object/rate.ts @@ -0,0 +1,16 @@ +export class Rate { + private _value: number; + + constructor(value: number) { + if (value <= 0) { + throw new Error('Invalid rate'); + } + this._value = value; + } + + get value() { + return this._value; + } +} + +export default Rate; \ No newline at end of file diff --git a/src/domain/value-object/symbol.ts b/src/domain/value-object/symbol.ts new file mode 100644 index 000000000..4f84f49a2 --- /dev/null +++ b/src/domain/value-object/symbol.ts @@ -0,0 +1,16 @@ +export class Symbol { + private _value: string; + + constructor(value: string) { + if (value.length < 1 || value.length > 5) { + throw new Error('Invalid symbol'); + } + this._value = value; + } + + get value() { + return this._value; + } +} + +export default Symbol; \ No newline at end of file diff --git a/test/unity/currenty-rate.test.ts b/test/unity/currenty-rate.test.ts index 204482221..80bc7212a 100644 --- a/test/unity/currenty-rate.test.ts +++ b/test/unity/currenty-rate.test.ts @@ -7,8 +7,40 @@ describe('Currency Rate', () => { expect(currencyRate).toBeDefined(); }); + it('should convert an amount to another currency', () => { + const usdCurrencyRate = CurrencyRate.create('US Dollar', 'USD', '$', 2, 1); + const brlCurrencyRate = CurrencyRate.create('Brazilian Real', 'BRL', 'R$', 2, 5.2); + expect(usdCurrencyRate.convertTo(brlCurrencyRate, 1)).toBe(5.20); + expect(usdCurrencyRate.convertTo(brlCurrencyRate, 2)).toBe(10.40); + expect(brlCurrencyRate.convertTo(usdCurrencyRate, 5.2)).toBe(1); + expect(brlCurrencyRate.convertTo(usdCurrencyRate, 10.40)).toBe(2); + }); + + it('should throw an error when the rate is invalid', () => { + expect(() => CurrencyRate.create('US Dollar', 'USD', '$', 2, 0)).toThrow('Invalid rate'); + expect(() => CurrencyRate.create('US Dollar', 'USD', '$', 2, -1)).toThrow('Invalid rate'); + }); + + it('should throw an error when the name is invalid', () => { + expect(() => CurrencyRate.create('', 'USD', '$', 2, 1)).toThrow('Invalid name'); + }); + + it('should throw an error when the code is invalid', () => { + expect(() => CurrencyRate.create('US Dollar', '', '$', 2, 1)).toThrow('Invalid code'); + }); + + it('should throw an error when the symbol is invalid', () => { + expect(() => CurrencyRate.create('US Dollar', 'USD', '', 2, 1)).toThrow('Invalid symbol'); + }); + + it('should throw an error when the decimal points is invalid', () => { + expect(() => CurrencyRate.create('US Dollar', 'USD', '$', -1, 1)).toThrow('Invalid decimal point'); + expect(() => CurrencyRate.create('US Dollar', 'USD', '$', 11, 1)).toThrow('Invalid decimal point'); + }); + it('should throw an error when the rate is invalid', () => { expect(() => CurrencyRate.create('US Dollar', 'USD', '$', 2, 0)).toThrow('Invalid rate'); + expect(() => CurrencyRate.create('US Dollar', 'USD', '$', 2, -1)).toThrow('Invalid rate'); }); }); \ No newline at end of file From 9a8e7cb3fa1ccc14b4db1bb8645f19aa4a8d66da Mon Sep 17 00:00:00 2001 From: Leandro Reis Date: Fri, 7 Jun 2024 10:01:23 -0300 Subject: [PATCH 18/43] refactor: improve EquivalentRateCalculator to throw InvalidParamError and test error scenarios --- .../service/equivalent-rate-calculator.ts | 6 ++++-- ...params.error.ts => invalid-param.error.ts} | 2 +- test/unity/create-currency-rate.test.ts | 19 +------------------ test/unity/equivalent-rate-calculator.test.ts | 18 ++++++++++++++---- 4 files changed, 20 insertions(+), 25 deletions(-) rename src/domain/errors/{invalid-params.error.ts => invalid-param.error.ts} (63%) diff --git a/src/application/service/equivalent-rate-calculator.ts b/src/application/service/equivalent-rate-calculator.ts index 1215556cb..79ba3b145 100644 --- a/src/application/service/equivalent-rate-calculator.ts +++ b/src/application/service/equivalent-rate-calculator.ts @@ -1,13 +1,15 @@ +import InvalidParamError from "@/domain/errors/invalid-param.error"; + export class EquivalentRateCalculator { constructor(readonly equivalentCurrencyRate: number) { } calculateRate(baseAmount: number, equivalentCurrencyAmount: number): number { if (baseAmount <= 0) { - throw new Error('Invalid attribute: baseAmount'); + throw new InvalidParamError('Invalid param: baseAmount'); } if (equivalentCurrencyAmount <= 0) { - throw new Error('Invalid attribute: equivalentCurrencyAmount'); + throw new InvalidParamError('Invalid param: equivalentCurrencyAmount'); } const rate = baseAmount / equivalentCurrencyAmount * this.equivalentCurrencyRate; return rate diff --git a/src/domain/errors/invalid-params.error.ts b/src/domain/errors/invalid-param.error.ts similarity index 63% rename from src/domain/errors/invalid-params.error.ts rename to src/domain/errors/invalid-param.error.ts index 62fa7e644..e9f2d7bf4 100644 --- a/src/domain/errors/invalid-params.error.ts +++ b/src/domain/errors/invalid-param.error.ts @@ -1,4 +1,4 @@ -export default class InvalidParamsError extends Error { +export default class InvalidParamError extends Error { constructor(message: string) { super(message); this.name = 'InvalidParamsError'; diff --git a/test/unity/create-currency-rate.test.ts b/test/unity/create-currency-rate.test.ts index 7f7377093..b13a30a73 100644 --- a/test/unity/create-currency-rate.test.ts +++ b/test/unity/create-currency-rate.test.ts @@ -3,6 +3,7 @@ import GetCurrencyRateByCode from "@/application/use-case/get-currency-rate-by-c import CurrencyRateRepositoryFake from "@/infra/repository/currency-rate-repository-fake"; describe('create-currency-rate', () => { + it('should create a currency', async () => { const currencyRateRepository = new CurrencyRateRepositoryFake(); const createCurrency = new CreateCurrencyRate(currencyRateRepository); @@ -41,22 +42,4 @@ describe('create-currency-rate', () => { await expect(createCurrency.execute(createCurrencyInput)).rejects.toThrow('Currency rate with code ANY already exists'); }); - it('should not create a currency with invalid attribute', async () => { - const currencyRateRepository = new CurrencyRateRepositoryFake(); - const createCurrency = new CreateCurrencyRate(currencyRateRepository); - const createCurrencyInput: CreateCurrencyRateDto = { - name: 'Any Currency', - code: 'ANY', - symbol: 'A$', - decimalPoints: 2, - baseAmount: -1.00, - equivalentCurrencyCode: "USD", - equivalentCurrencyAmount: 1.00, - } - await expect(createCurrency.execute(createCurrencyInput)).rejects.toThrow('Invalid attribute: baseAmount'); - createCurrencyInput.baseAmount = 1.00; - createCurrencyInput.equivalentCurrencyAmount = -1.00; - await expect(createCurrency.execute(createCurrencyInput)).rejects.toThrow('Invalid attribute: equivalentCurrencyAmount'); - }); - }); \ No newline at end of file diff --git a/test/unity/equivalent-rate-calculator.test.ts b/test/unity/equivalent-rate-calculator.test.ts index 1a13bcbe7..dbf90e57b 100644 --- a/test/unity/equivalent-rate-calculator.test.ts +++ b/test/unity/equivalent-rate-calculator.test.ts @@ -2,16 +2,26 @@ import { EquivalentRateCalculator } from "@/application/service/equivalent-rate- describe('equivalent-rate-calculator', () => { - it('should calculate the equivalent rate R$', async () => { + it('should calculate the equivalent rate of BRL in USD', async () => { const equivalentRateCalculator = new EquivalentRateCalculator(1); - const equivalentRate = equivalentRateCalculator.calculateRate(10.40, 2.00); - expect(equivalentRate).toBe(5.20); + expect(equivalentRateCalculator.calculateRate(5.20, 1.00)).toBe(5.20); + expect(equivalentRateCalculator.calculateRate(10.40, 2.00)).toBe(5.20); }); - it('should calculate the equivalent rate of GTA$', async () => { + it('should calculate the equivalent rate of GTA$ in BRL', async () => { const equivalentRateCalculator = new EquivalentRateCalculator(5.20); const equivalentRate = equivalentRateCalculator.calculateRate(1250000.00, 83.50); expect(equivalentRate).toBe(77844.31137724551); }); + it('should throw an error when the base amount is invalid', async () => { + const equivalentRateCalculator = new EquivalentRateCalculator(1); + expect(() => equivalentRateCalculator.calculateRate(-1, 1)).toThrow('Invalid param: baseAmount'); + }); + + it('should throw an error when the equivalent currency amount is invalid', async () => { + const equivalentRateCalculator = new EquivalentRateCalculator(1); + expect(() => equivalentRateCalculator.calculateRate(1, -1)).toThrow('Invalid param: equivalentCurrencyAmount'); + }); + }); \ No newline at end of file From d1e786f81af79420aba4b6f8cc90cb228fccad82 Mon Sep 17 00:00:00 2001 From: Leandro Reis Date: Mon, 10 Jun 2024 15:36:10 -0300 Subject: [PATCH 19/43] feat: add the Express HTTP server to serve the currency endpoints. --- package.json | 7 +- src/application/use-case/convert-currency.ts | 8 +- src/infra/http-server/currency.controller.ts | 46 ++ src/infra/http-server/express-adapter.ts | 30 + src/infra/http-server/http-server.ts | 14 + src/main/main-express.ts | 16 + test/integration/main-express.test.ts | 62 ++ yarn.lock | 707 ++++++++++++++++++- 8 files changed, 880 insertions(+), 10 deletions(-) create mode 100644 src/infra/http-server/currency.controller.ts create mode 100644 src/infra/http-server/express-adapter.ts create mode 100644 src/infra/http-server/http-server.ts create mode 100644 src/main/main-express.ts create mode 100644 test/integration/main-express.test.ts diff --git a/package.json b/package.json index 79c4859d9..fc6b60a32 100644 --- a/package.json +++ b/package.json @@ -7,18 +7,23 @@ "license": "MIT", "scripts": { "dev": "ts-node -r tsconfig-paths/register src/index.ts", + "dev:express": "ts-node-dev -r tsconfig-paths/register src/main/main-express.ts", "test": "jest", "test:watch": "jest --watch --detectOpenHandles" }, "dependencies": { + "axios": "^1.7.2", "crypto": "^1.0.1", - "currency.js": "^2.0.4" + "currency.js": "^2.0.4", + "express": "^4.19.2" }, "devDependencies": { + "@types/express": "^4.17.21", "@types/jest": "^29.5.12", "jest": "^29.7.0", "ts-jest": "^29.1.4", "ts-node": "^10.9.2", + "ts-node-dev": "^2.0.0", "tsconfig-paths": "^4.2.0", "typescript": "^5.4.5" } diff --git a/src/application/use-case/convert-currency.ts b/src/application/use-case/convert-currency.ts index a4d9fae4e..ca10ff18e 100644 --- a/src/application/use-case/convert-currency.ts +++ b/src/application/use-case/convert-currency.ts @@ -8,7 +8,11 @@ export default class ConvertCurrency { const exchangeFrom = await this.currencyRateRepository.findByCode(input.from); const exchangeTo = await this.currencyRateRepository.findByCode(input.to); const convertedAmount = exchangeFrom.convertTo(exchangeTo, input.amount); - return { value: convertedAmount } + return { + value: convertedAmount, + symbol: exchangeTo.symbol, + decimalPoints: exchangeTo.decimalPoints + } } } @@ -20,4 +24,6 @@ type Input = { type Output = { value: number; + symbol: string; + decimalPoints: number; } \ No newline at end of file diff --git a/src/infra/http-server/currency.controller.ts b/src/infra/http-server/currency.controller.ts new file mode 100644 index 000000000..19bc99acf --- /dev/null +++ b/src/infra/http-server/currency.controller.ts @@ -0,0 +1,46 @@ +import ConvertCurrency from "@/application/use-case/convert-currency"; +import CreateCurrencyRate, { CreateCurrencyRateDto } from "@/application/use-case/create-currency-rate"; +import { DeleteCurrencyRateByCode } from "@/application/use-case/delete-currency-rate-by-code"; +import GetCurrencyRateByCode from "@/application/use-case/get-currency-rate-by-code"; +import { HttpServer } from "@/infra/http-server/http-server"; + +export class CurrencyController { + constructor( + readonly httpServer: HttpServer, + readonly convertCurrency: ConvertCurrency, + readonly createCurrencyRate: CreateCurrencyRate, + readonly getCurrencyRateByCode: GetCurrencyRateByCode, + readonly deleteCurrencyRateByCode: DeleteCurrencyRateByCode + ) { + httpServer.register('get', '/currencies/convert', async ({ query }) => { + const { from, to, amount } = query; + const input = { from, to, amount }; + const output = await this.convertCurrency.execute(input); + return output + }) + httpServer.register('post', '/currencies', async ({ body }) => { + const input: CreateCurrencyRateDto = { + name: body.name, + code: body.code, + symbol: body.symbol, + decimalPoints: body.decimalPoints, + baseAmount: body.baseAmount, + equivalentCurrencyAmount: body.equivalentCurrencyAmount, + equivalentCurrencyCode: body.equivalentCurrencyCode + } + const output = await this.createCurrencyRate.execute(input); + return output + }) + httpServer.register('get', '/currencies/{code}', async ({ params }) => { + const { code } = params; + const output = await this.getCurrencyRateByCode.execute(code); + return output + }) + httpServer.register('delete', '/currencies/{code}', async ({ params }) => { + const { code } = params; + await this.deleteCurrencyRateByCode.execute(code); + return { message: 'Currency deleted' } + }) + } + +} \ No newline at end of file diff --git a/src/infra/http-server/express-adapter.ts b/src/infra/http-server/express-adapter.ts new file mode 100644 index 000000000..ef586031f --- /dev/null +++ b/src/infra/http-server/express-adapter.ts @@ -0,0 +1,30 @@ +import { Controller, HttpServer, Method } from '@/infra/http-server/http-server'; + +import express, { Express } from 'express'; + +export class ExpressAdapter implements HttpServer { + private app: Express; + + constructor() { + this.app = express(); + this.app.use(express.json()); + } + + register(method: Method, route: string, handler: Controller): void { + this.app[method](route.replace('}', '').replace('{', ':'), async (req, res) => { + try { + const result = await handler({ query: req.query, params: req.params, body: req.body }); + res.json(result); + } catch (error: any) { + res.status(500).json({ message: error.message }); + } + }); + } + + + listen(port: number): void { + this.app.listen(port, () => { + console.log(`Server running on port ${port}`); + }); + } +} diff --git a/src/infra/http-server/http-server.ts b/src/infra/http-server/http-server.ts new file mode 100644 index 000000000..0f51168a6 --- /dev/null +++ b/src/infra/http-server/http-server.ts @@ -0,0 +1,14 @@ +export interface HttpServer { + register(method: Method, route: string, handler: Controller): void; + listen(port: number): void; +} + +export type Method = 'get' | 'post' | 'put' | 'delete'; + +export type Controller = (args: ControllerParams) => Promise; + +export type ControllerParams = { + query: { [key: string]: any } + params: { [key: string]: string }, + body: { [key: string]: any } +} \ No newline at end of file diff --git a/src/main/main-express.ts b/src/main/main-express.ts new file mode 100644 index 000000000..726043016 --- /dev/null +++ b/src/main/main-express.ts @@ -0,0 +1,16 @@ +import ConvertCurrency from "@/application/use-case/convert-currency"; +import CreateCurrencyRate from "@/application/use-case/create-currency-rate"; +import { DeleteCurrencyRateByCode } from "@/application/use-case/delete-currency-rate-by-code"; +import GetCurrencyRateByCode from "@/application/use-case/get-currency-rate-by-code"; +import { CurrencyController } from "@/infra/http-server/currency.controller"; +import { ExpressAdapter } from "@/infra/http-server/express-adapter"; +import CurrencyRateRepositoryFake from "@/infra/repository/currency-rate-repository-fake"; + +const httpServer = new ExpressAdapter(); +const currencyRateRepository = new CurrencyRateRepositoryFake(); +const currencyConverter = new ConvertCurrency(currencyRateRepository) +const createCurrencyRate = new CreateCurrencyRate(currencyRateRepository) +const getCurrencyRateByCode = new GetCurrencyRateByCode(currencyRateRepository) +const deleteCurrencyRateByCode = new DeleteCurrencyRateByCode(currencyRateRepository) +new CurrencyController(httpServer, currencyConverter, createCurrencyRate, getCurrencyRateByCode, deleteCurrencyRateByCode) +httpServer.listen(3000); \ No newline at end of file diff --git a/test/integration/main-express.test.ts b/test/integration/main-express.test.ts new file mode 100644 index 000000000..160696058 --- /dev/null +++ b/test/integration/main-express.test.ts @@ -0,0 +1,62 @@ +import axios from 'axios'; + +describe('Main Express', () => { + + it('should convert USD to BRL', async () => { + const params = { from: 'USD', to: 'BRL', amount: 10 }; + const response = await axios.get('http://localhost:3000/currencies/convert', { params }); + const output = response.data; + expect(output).toEqual({ value: 52, symbol: 'R$', decimalPoints: 2 }); + }); + + it('should convert BRL to USD', async () => { + const params = { from: 'BRL', to: 'USD', amount: 10.4 }; + const response = await axios.get('http://localhost:3000/currencies/convert', { params }); + const output = response.data; + expect(output).toEqual({ value: 2, symbol: '$', decimalPoints: 2 }); + }); + + it('should create, get and delete a currency rate', async () => { + const payload = { + name: 'Any Currency', + code: 'ANY', + symbol: '$', + decimalPoints: 2, + baseAmount: 2, + equivalentCurrencyAmount: 1, + equivalentCurrencyCode: 'USD' + }; + const createResponse = await axios.post('http://localhost:3000/currencies', payload); + const createOutput = createResponse.data; + expect(createOutput.currencyRateId).toBeDefined(); + const getResponse = await axios.get(`http://localhost:3000/currencies/${payload.code}`); + const getOutput = getResponse.data; + expect(getOutput.name).toEqual(payload.name); + expect(getOutput.code).toEqual(payload.code); + expect(getOutput.symbol).toEqual(payload.symbol); + expect(getOutput.decimalPoints).toEqual(payload.decimalPoints); + expect(getOutput.rate).toEqual(2); + const deleteResponse = await axios.delete(`http://localhost:3000/currencies/${getOutput.code}`); + const deleteOutput = deleteResponse.data; + expect(deleteOutput).toEqual({ message: 'Currency deleted' }); + }); + + it('should throw an error when trying to convert with invalid currency', async () => { + const params = { from: 'INVALID', to: 'BRL', amount: 10 }; + try { + await axios.get('http://localhost:3000/currencies/convert', { params }); + } catch (error: any) { + expect(error.response.data).toEqual({ message: 'Currency rate not found: INVALID' }); + } + }); + + it('should throw an error when trying to convert with invalid amount', async () => { + const params = { from: 'USD', to: 'BRL', amount: -10 }; + try { + await axios.get('http://localhost:3000/currencies/convert', { params }); + } catch (error: any) { + expect(error.response.data).toEqual({ message: 'Invalid amount' }); + } + }); + +}); \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index cb71a89e4..6bf74fc67 100644 --- a/yarn.lock +++ b/yarn.lock @@ -624,6 +624,41 @@ dependencies: "@babel/types" "^7.20.7" +"@types/body-parser@*": + version "1.19.5" + resolved "https://registry.yarnpkg.com/@types/body-parser/-/body-parser-1.19.5.tgz#04ce9a3b677dc8bd681a17da1ab9835dc9d3ede4" + integrity sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg== + dependencies: + "@types/connect" "*" + "@types/node" "*" + +"@types/connect@*": + version "3.4.38" + resolved "https://registry.yarnpkg.com/@types/connect/-/connect-3.4.38.tgz#5ba7f3bc4fbbdeaff8dded952e5ff2cc53f8d858" + integrity sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug== + dependencies: + "@types/node" "*" + +"@types/express-serve-static-core@^4.17.33": + version "4.19.3" + resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.19.3.tgz#e469a13e4186c9e1c0418fb17be8bc8ff1b19a7a" + integrity sha512-KOzM7MhcBFlmnlr/fzISFF5vGWVSvN6fTd4T+ExOt08bA/dA5kpSzY52nMsI1KDFmUREpJelPYyuslLRSjjgCg== + dependencies: + "@types/node" "*" + "@types/qs" "*" + "@types/range-parser" "*" + "@types/send" "*" + +"@types/express@^4.17.21": + version "4.17.21" + resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.21.tgz#c26d4a151e60efe0084b23dc3369ebc631ed192d" + integrity sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ== + dependencies: + "@types/body-parser" "*" + "@types/express-serve-static-core" "^4.17.33" + "@types/qs" "*" + "@types/serve-static" "*" + "@types/graceful-fs@^4.1.3": version "4.1.9" resolved "https://registry.yarnpkg.com/@types/graceful-fs/-/graceful-fs-4.1.9.tgz#2a06bc0f68a20ab37b3e36aa238be6abdf49e8b4" @@ -631,6 +666,11 @@ dependencies: "@types/node" "*" +"@types/http-errors@*": + version "2.0.4" + resolved "https://registry.yarnpkg.com/@types/http-errors/-/http-errors-2.0.4.tgz#7eb47726c391b7345a6ec35ad7f4de469cf5ba4f" + integrity sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA== + "@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0", "@types/istanbul-lib-coverage@^2.0.1": version "2.0.6" resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz#7739c232a1fee9b4d3ce8985f314c0c6d33549d7" @@ -658,6 +698,11 @@ expect "^29.0.0" pretty-format "^29.0.0" +"@types/mime@^1": + version "1.3.5" + resolved "https://registry.yarnpkg.com/@types/mime/-/mime-1.3.5.tgz#1ef302e01cf7d2b5a0fa526790c9123bf1d06690" + integrity sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w== + "@types/node@*": version "20.12.13" resolved "https://registry.yarnpkg.com/@types/node/-/node-20.12.13.tgz#90ed3b8a4e52dd3c5dc5a42dde5b85b74ad8ed88" @@ -665,11 +710,48 @@ dependencies: undici-types "~5.26.4" +"@types/qs@*": + version "6.9.15" + resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.15.tgz#adde8a060ec9c305a82de1babc1056e73bd64dce" + integrity sha512-uXHQKES6DQKKCLh441Xv/dwxOq1TVS3JPUMlEqoEglvlhR6Mxnlew/Xq/LRVHpLyk7iK3zODe1qYHIMltO7XGg== + +"@types/range-parser@*": + version "1.2.7" + resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.7.tgz#50ae4353eaaddc04044279812f52c8c65857dbcb" + integrity sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ== + +"@types/send@*": + version "0.17.4" + resolved "https://registry.yarnpkg.com/@types/send/-/send-0.17.4.tgz#6619cd24e7270793702e4e6a4b958a9010cfc57a" + integrity sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA== + dependencies: + "@types/mime" "^1" + "@types/node" "*" + +"@types/serve-static@*": + version "1.15.7" + resolved "https://registry.yarnpkg.com/@types/serve-static/-/serve-static-1.15.7.tgz#22174bbd74fb97fe303109738e9b5c2f3064f714" + integrity sha512-W8Ym+h8nhuRwaKPaDw34QUkwsGi6Rc4yYqvKFo5rm2FUEhCFbzVWrxXUxuKK8TASjWsysJY0nsmNCGhCOIsrOw== + dependencies: + "@types/http-errors" "*" + "@types/node" "*" + "@types/send" "*" + "@types/stack-utils@^2.0.0": version "2.0.3" resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.3.tgz#6209321eb2c1712a7e7466422b8cb1fc0d9dd5d8" integrity sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw== +"@types/strip-bom@^3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@types/strip-bom/-/strip-bom-3.0.0.tgz#14a8ec3956c2e81edb7520790aecf21c290aebd2" + integrity sha512-xevGOReSYGM7g/kUBZzPqCrR/KYAo+F0yiPc85WFTJa0MSLtyFTVTU6cJu/aV4mid7IffDIWqo69THF2o4JiEQ== + +"@types/strip-json-comments@0.0.30": + version "0.0.30" + resolved "https://registry.yarnpkg.com/@types/strip-json-comments/-/strip-json-comments-0.0.30.tgz#9aa30c04db212a9a0649d6ae6fd50accc40748a1" + integrity sha512-7NQmHra/JILCd1QqpSzl8+mJRc8ZHz3uDm8YV1Ks9IhK0epEiTw8aIErbvH9PI+6XbqhyIQy3462nEsn7UVzjQ== + "@types/yargs-parser@*": version "21.0.3" resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-21.0.3.tgz#815e30b786d2e8f0dcd85fd5bcf5e1a04d008f15" @@ -682,6 +764,14 @@ dependencies: "@types/yargs-parser" "*" +accepts@~1.3.8: + version "1.3.8" + resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.8.tgz#0bf0be125b67014adcb0b0921e62db7bffe16b2e" + integrity sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw== + dependencies: + mime-types "~2.1.34" + negotiator "0.6.3" + acorn-walk@^8.1.1: version "8.3.2" resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.3.2.tgz#7703af9415f1b6db9315d6895503862e231d34aa" @@ -723,7 +813,7 @@ ansi-styles@^5.0.0: resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-5.2.0.tgz#07449690ad45777d1924ac2abb2fc8895dba836b" integrity sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA== -anymatch@^3.0.3: +anymatch@^3.0.3, anymatch@~3.1.2: version "3.1.3" resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.3.tgz#790c58b19ba1720a84205b57c618d5ad8524973e" integrity sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw== @@ -743,6 +833,25 @@ argparse@^1.0.7: dependencies: sprintf-js "~1.0.2" +array-flatten@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2" + integrity sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg== + +asynckit@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" + integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q== + +axios@^1.7.2: + version "1.7.2" + resolved "https://registry.yarnpkg.com/axios/-/axios-1.7.2.tgz#b625db8a7051fbea61c35a3cbb3a1daa7b9c7621" + integrity sha512-2A8QhOMrbomlDuiLeK9XibIBzuHeRcqqNOHp0Cyp5EoJ1IFDh+XZH3A6BkXtv0K4gFGCI0Y4BM7B1wOEi0Rmgw== + dependencies: + follow-redirects "^1.15.6" + form-data "^4.0.0" + proxy-from-env "^1.1.0" + babel-jest@^29.7.0: version "29.7.0" resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-29.7.0.tgz#f4369919225b684c56085998ac63dbd05be020d5" @@ -808,6 +917,29 @@ balanced-match@^1.0.0: resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== +binary-extensions@^2.0.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.3.0.tgz#f6e14a97858d327252200242d4ccfe522c445522" + integrity sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw== + +body-parser@1.20.2: + version "1.20.2" + resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.2.tgz#6feb0e21c4724d06de7ff38da36dad4f57a747fd" + integrity sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA== + dependencies: + bytes "3.1.2" + content-type "~1.0.5" + debug "2.6.9" + depd "2.0.0" + destroy "1.2.0" + http-errors "2.0.0" + iconv-lite "0.4.24" + on-finished "2.4.1" + qs "6.11.0" + raw-body "2.5.2" + type-is "~1.6.18" + unpipe "1.0.0" + brace-expansion@^1.1.7: version "1.1.11" resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" @@ -816,7 +948,7 @@ brace-expansion@^1.1.7: balanced-match "^1.0.0" concat-map "0.0.1" -braces@^3.0.3: +braces@^3.0.3, braces@~3.0.2: version "3.0.3" resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.3.tgz#490332f40919452272d55a8480adc0c441358789" integrity sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA== @@ -852,6 +984,22 @@ buffer-from@^1.0.0: resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== +bytes@3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.2.tgz#8b0beeb98605adf1b128fa4386403c009e0221a5" + integrity sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg== + +call-bind@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.7.tgz#06016599c40c56498c18769d2730be242b6fa3b9" + integrity sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w== + dependencies: + es-define-property "^1.0.0" + es-errors "^1.3.0" + function-bind "^1.1.2" + get-intrinsic "^1.2.4" + set-function-length "^1.2.1" + callsites@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" @@ -894,6 +1042,21 @@ char-regex@^1.0.2: resolved "https://registry.yarnpkg.com/char-regex/-/char-regex-1.0.2.tgz#d744358226217f981ed58f479b1d6bcc29545dcf" integrity sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw== +chokidar@^3.5.1: + version "3.6.0" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.6.0.tgz#197c6cc669ef2a8dc5e7b4d97ee4e092c3eb0d5b" + integrity sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw== + dependencies: + anymatch "~3.1.2" + braces "~3.0.2" + glob-parent "~5.1.2" + is-binary-path "~2.1.0" + is-glob "~4.0.1" + normalize-path "~3.0.0" + readdirp "~3.6.0" + optionalDependencies: + fsevents "~2.3.2" + ci-info@^3.2.0: version "3.9.0" resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.9.0.tgz#4279a62028a7b1f262f3473fc9605f5e218c59b4" @@ -947,16 +1110,45 @@ color-name@~1.1.4: resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== +combined-stream@^1.0.8: + version "1.0.8" + resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" + integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== + dependencies: + delayed-stream "~1.0.0" + concat-map@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== +content-disposition@0.5.4: + version "0.5.4" + resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.4.tgz#8b82b4efac82512a02bb0b1dcec9d2c5e8eb5bfe" + integrity sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ== + dependencies: + safe-buffer "5.2.1" + +content-type@~1.0.4, content-type@~1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.5.tgz#8b773162656d1d1086784c8f23a54ce6d73d7918" + integrity sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA== + convert-source-map@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-2.0.0.tgz#4b560f649fc4e918dd0ab75cf4961e8bc882d82a" integrity sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg== +cookie-signature@1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" + integrity sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ== + +cookie@0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.6.0.tgz#2798b04b071b0ecbff0dbb62a505a8efa4e19051" + integrity sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw== + create-jest@^29.7.0: version "29.7.0" resolved "https://registry.yarnpkg.com/create-jest/-/create-jest-29.7.0.tgz#a355c5b3cb1e1af02ba177fe7afd7feee49a5320" @@ -994,6 +1186,13 @@ currency.js@^2.0.4: resolved "https://registry.yarnpkg.com/currency.js/-/currency.js-2.0.4.tgz#a8a4d69be3b2e509bf67a560c78220bc04809cf1" integrity sha512-6/OplJYgJ0RUlli74d93HJ/OsKVBi8lB1+Z6eJYS1YZzBuIp4qKKHpJ7ad+GvTlWmLR/hLJOWTykN5Nm8NJ7+w== +debug@2.6.9: + version "2.6.9" + resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" + integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== + dependencies: + ms "2.0.0" + debug@^4.1.0, debug@^4.1.1, debug@^4.3.1: version "4.3.4" resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" @@ -1011,6 +1210,30 @@ deepmerge@^4.2.2: resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.3.1.tgz#44b5f2147cd3b00d4b56137685966f26fd25dd4a" integrity sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A== +define-data-property@^1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/define-data-property/-/define-data-property-1.1.4.tgz#894dc141bb7d3060ae4366f6a0107e68fbe48c5e" + integrity sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A== + dependencies: + es-define-property "^1.0.0" + es-errors "^1.3.0" + gopd "^1.0.1" + +delayed-stream@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" + integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ== + +depd@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df" + integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw== + +destroy@1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.2.0.tgz#4803735509ad8be552934c67df614f94e66fa015" + integrity sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg== + detect-newline@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-3.1.0.tgz#576f5dfc63ae1a192ff192d8ad3af6308991b651" @@ -1026,6 +1249,18 @@ diff@^4.0.1: resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d" integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A== +dynamic-dedupe@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/dynamic-dedupe/-/dynamic-dedupe-0.3.0.tgz#06e44c223f5e4e94d78ef9db23a6515ce2f962a1" + integrity sha512-ssuANeD+z97meYOqd50e04Ze5qp4bPqo8cCkI4TRjZkzAUgIDTrXV1R8QCdINpiI+hw14+rYazvTRdQrz0/rFQ== + dependencies: + xtend "^4.0.0" + +ee-first@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" + integrity sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow== + electron-to-chromium@^1.4.668: version "1.4.786" resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.786.tgz#974d7eeac61c5ffa285dc55555d06b97b05f6831" @@ -1041,6 +1276,11 @@ emoji-regex@^8.0.0: resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== +encodeurl@~1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" + integrity sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w== + error-ex@^1.3.1: version "1.3.2" resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf" @@ -1048,11 +1288,28 @@ error-ex@^1.3.1: dependencies: is-arrayish "^0.2.1" +es-define-property@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/es-define-property/-/es-define-property-1.0.0.tgz#c7faefbdff8b2696cf5f46921edfb77cc4ba3845" + integrity sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ== + dependencies: + get-intrinsic "^1.2.4" + +es-errors@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/es-errors/-/es-errors-1.3.0.tgz#05f75a25dab98e4fb1dcd5e1472c0546d5057c8f" + integrity sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw== + escalade@^3.1.1, escalade@^3.1.2: version "3.1.2" resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.2.tgz#54076e9ab29ea5bf3d8f1ed62acffbb88272df27" integrity sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA== +escape-html@~1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" + integrity sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow== + escape-string-regexp@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" @@ -1068,6 +1325,11 @@ esprima@^4.0.0: resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== +etag@~1.8.1: + version "1.8.1" + resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" + integrity sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg== + execa@^5.0.0: version "5.1.1" resolved "https://registry.yarnpkg.com/execa/-/execa-5.1.1.tgz#f80ad9cbf4298f7bd1d4c9555c21e93741c411dd" @@ -1099,6 +1361,43 @@ expect@^29.0.0, expect@^29.7.0: jest-message-util "^29.7.0" jest-util "^29.7.0" +express@^4.19.2: + version "4.19.2" + resolved "https://registry.yarnpkg.com/express/-/express-4.19.2.tgz#e25437827a3aa7f2a827bc8171bbbb664a356465" + integrity sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q== + dependencies: + accepts "~1.3.8" + array-flatten "1.1.1" + body-parser "1.20.2" + content-disposition "0.5.4" + content-type "~1.0.4" + cookie "0.6.0" + cookie-signature "1.0.6" + debug "2.6.9" + depd "2.0.0" + encodeurl "~1.0.2" + escape-html "~1.0.3" + etag "~1.8.1" + finalhandler "1.2.0" + fresh "0.5.2" + http-errors "2.0.0" + merge-descriptors "1.0.1" + methods "~1.1.2" + on-finished "2.4.1" + parseurl "~1.3.3" + path-to-regexp "0.1.7" + proxy-addr "~2.0.7" + qs "6.11.0" + range-parser "~1.2.1" + safe-buffer "5.2.1" + send "0.18.0" + serve-static "1.15.0" + setprototypeof "1.2.0" + statuses "2.0.1" + type-is "~1.6.18" + utils-merge "1.0.1" + vary "~1.1.2" + fast-json-stable-stringify@2.x, fast-json-stable-stringify@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" @@ -1118,6 +1417,19 @@ fill-range@^7.1.1: dependencies: to-regex-range "^5.0.1" +finalhandler@1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.2.0.tgz#7d23fe5731b207b4640e4fcd00aec1f9207a7b32" + integrity sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg== + dependencies: + debug "2.6.9" + encodeurl "~1.0.2" + escape-html "~1.0.3" + on-finished "2.4.1" + parseurl "~1.3.3" + statuses "2.0.1" + unpipe "~1.0.0" + find-up@^4.0.0, find-up@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19" @@ -1126,12 +1438,36 @@ find-up@^4.0.0, find-up@^4.1.0: locate-path "^5.0.0" path-exists "^4.0.0" +follow-redirects@^1.15.6: + version "1.15.6" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.6.tgz#7f815c0cda4249c74ff09e95ef97c23b5fd0399b" + integrity sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA== + +form-data@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.0.tgz#93919daeaf361ee529584b9b31664dc12c9fa452" + integrity sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww== + dependencies: + asynckit "^0.4.0" + combined-stream "^1.0.8" + mime-types "^2.1.12" + +forwarded@0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.2.0.tgz#2269936428aad4c15c7ebe9779a84bf0b2a81811" + integrity sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow== + +fresh@0.5.2: + version "0.5.2" + resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" + integrity sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q== + fs.realpath@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== -fsevents@^2.3.2: +fsevents@^2.3.2, fsevents@~2.3.2: version "2.3.3" resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6" integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== @@ -1151,6 +1487,17 @@ get-caller-file@^2.0.5: resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== +get-intrinsic@^1.1.3, get-intrinsic@^1.2.4: + version "1.2.4" + resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.2.4.tgz#e385f5a4b5227d449c3eabbad05494ef0abbeadd" + integrity sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ== + dependencies: + es-errors "^1.3.0" + function-bind "^1.1.2" + has-proto "^1.0.1" + has-symbols "^1.0.3" + hasown "^2.0.0" + get-package-type@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/get-package-type/-/get-package-type-0.1.0.tgz#8de2d803cff44df3bc6c456e6668b36c3926e11a" @@ -1161,6 +1508,13 @@ get-stream@^6.0.0: resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7" integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg== +glob-parent@~5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" + integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== + dependencies: + is-glob "^4.0.1" + glob@^7.1.3, glob@^7.1.4: version "7.2.3" resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" @@ -1178,6 +1532,13 @@ globals@^11.1.0: resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== +gopd@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.0.1.tgz#29ff76de69dac7489b7c0918a5788e56477c332c" + integrity sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA== + dependencies: + get-intrinsic "^1.1.3" + graceful-fs@^4.2.9: version "4.2.11" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" @@ -1193,6 +1554,23 @@ has-flag@^4.0.0: resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== +has-property-descriptors@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz#963ed7d071dc7bf5f084c5bfbe0d1b6222586854" + integrity sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg== + dependencies: + es-define-property "^1.0.0" + +has-proto@^1.0.1: + version "1.0.3" + resolved "https://registry.yarnpkg.com/has-proto/-/has-proto-1.0.3.tgz#b31ddfe9b0e6e9914536a6ab286426d0214f77fd" + integrity sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q== + +has-symbols@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.3.tgz#bb7b2c4349251dce87b125f7bdf874aa7c8b39f8" + integrity sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A== + hasown@^2.0.0: version "2.0.2" resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.2.tgz#003eaf91be7adc372e84ec59dc37252cedb80003" @@ -1205,11 +1583,29 @@ html-escaper@^2.0.0: resolved "https://registry.yarnpkg.com/html-escaper/-/html-escaper-2.0.2.tgz#dfd60027da36a36dfcbe236262c00a5822681453" integrity sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg== +http-errors@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-2.0.0.tgz#b7774a1486ef73cf7667ac9ae0858c012c57b9d3" + integrity sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ== + dependencies: + depd "2.0.0" + inherits "2.0.4" + setprototypeof "1.2.0" + statuses "2.0.1" + toidentifier "1.0.1" + human-signals@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0" integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw== +iconv-lite@0.4.24: + version "0.4.24" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" + integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== + dependencies: + safer-buffer ">= 2.1.2 < 3" + import-local@^3.0.2: version "3.1.0" resolved "https://registry.yarnpkg.com/import-local/-/import-local-3.1.0.tgz#b4479df8a5fd44f6cdce24070675676063c95cb4" @@ -1231,16 +1627,28 @@ inflight@^1.0.4: once "^1.3.0" wrappy "1" -inherits@2: +inherits@2, inherits@2.0.4: version "2.0.4" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== +ipaddr.js@1.9.1: + version "1.9.1" + resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3" + integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g== + is-arrayish@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" integrity sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg== +is-binary-path@~2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09" + integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw== + dependencies: + binary-extensions "^2.0.0" + is-core-module@^2.13.0: version "2.13.1" resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.13.1.tgz#ad0d7532c6fea9da1ebdc82742d74525c6273384" @@ -1248,6 +1656,11 @@ is-core-module@^2.13.0: dependencies: hasown "^2.0.0" +is-extglob@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" + integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== + is-fullwidth-code-point@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" @@ -1258,6 +1671,13 @@ is-generator-fn@^2.0.0: resolved "https://registry.yarnpkg.com/is-generator-fn/-/is-generator-fn-2.1.0.tgz#7d140adc389aaf3011a8f2a2a4cfa6faadffb118" integrity sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ== +is-glob@^4.0.1, is-glob@~4.0.1: + version "4.0.3" + resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" + integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== + dependencies: + is-extglob "^2.1.1" + is-number@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" @@ -1765,11 +2185,26 @@ makeerror@1.0.12: dependencies: tmpl "1.0.5" +media-typer@0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" + integrity sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ== + +merge-descriptors@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61" + integrity sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w== + merge-stream@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== +methods@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" + integrity sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w== + micromatch@^4.0.4: version "4.0.7" resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.7.tgz#33e8190d9fe474a9895525f5618eee136d46c2e5" @@ -1778,6 +2213,23 @@ micromatch@^4.0.4: braces "^3.0.3" picomatch "^2.3.1" +mime-db@1.52.0: + version "1.52.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" + integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== + +mime-types@^2.1.12, mime-types@~2.1.24, mime-types@~2.1.34: + version "2.1.35" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" + integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== + dependencies: + mime-db "1.52.0" + +mime@1.6.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" + integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== + mimic-fn@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" @@ -1795,16 +2247,36 @@ minimist@^1.2.6: resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c" integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== +mkdirp@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" + integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== + +ms@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" + integrity sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A== + ms@2.1.2: version "2.1.2" resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== +ms@2.1.3: + version "2.1.3" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" + integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== + natural-compare@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw== +negotiator@0.6.3: + version "0.6.3" + resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.3.tgz#58e323a72fedc0d6f9cd4d31fe49f51479590ccd" + integrity sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg== + node-int64@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b" @@ -1815,7 +2287,7 @@ node-releases@^2.0.14: resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.14.tgz#2ffb053bceb8b2be8495ece1ab6ce600c4461b0b" integrity sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw== -normalize-path@^3.0.0: +normalize-path@^3.0.0, normalize-path@~3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== @@ -1827,6 +2299,18 @@ npm-run-path@^4.0.1: dependencies: path-key "^3.0.0" +object-inspect@^1.13.1: + version "1.13.1" + resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.13.1.tgz#b96c6109324ccfef6b12216a956ca4dc2ff94bc2" + integrity sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ== + +on-finished@2.4.1: + version "2.4.1" + resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.4.1.tgz#58c8c44116e54845ad57f14ab10b03533184ac3f" + integrity sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg== + dependencies: + ee-first "1.1.1" + once@^1.3.0: version "1.4.0" resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" @@ -1877,6 +2361,11 @@ parse-json@^5.2.0: json-parse-even-better-errors "^2.3.0" lines-and-columns "^1.1.6" +parseurl@~1.3.3: + version "1.3.3" + resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4" + integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ== + path-exists@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" @@ -1897,12 +2386,17 @@ path-parse@^1.0.7: resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== +path-to-regexp@0.1.7: + version "0.1.7" + resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" + integrity sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ== + picocolors@^1.0.0, picocolors@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.1.tgz#a8ad579b571952f0e5d25892de5445bcfe25aaa1" integrity sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew== -picomatch@^2.0.4, picomatch@^2.2.3, picomatch@^2.3.1: +picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.2.3, picomatch@^2.3.1: version "2.3.1" resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== @@ -1936,16 +2430,58 @@ prompts@^2.0.1: kleur "^3.0.3" sisteransi "^1.0.5" +proxy-addr@~2.0.7: + version "2.0.7" + resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.7.tgz#f19fe69ceab311eeb94b42e70e8c2070f9ba1025" + integrity sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg== + dependencies: + forwarded "0.2.0" + ipaddr.js "1.9.1" + +proxy-from-env@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2" + integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg== + pure-rand@^6.0.0: version "6.1.0" resolved "https://registry.yarnpkg.com/pure-rand/-/pure-rand-6.1.0.tgz#d173cf23258231976ccbdb05247c9787957604f2" integrity sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA== +qs@6.11.0: + version "6.11.0" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.11.0.tgz#fd0d963446f7a65e1367e01abd85429453f0c37a" + integrity sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q== + dependencies: + side-channel "^1.0.4" + +range-parser@~1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031" + integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg== + +raw-body@2.5.2: + version "2.5.2" + resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.5.2.tgz#99febd83b90e08975087e8f1f9419a149366b68a" + integrity sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA== + dependencies: + bytes "3.1.2" + http-errors "2.0.0" + iconv-lite "0.4.24" + unpipe "1.0.0" + react-is@^18.0.0: version "18.3.1" resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.3.1.tgz#e83557dc12eae63a99e003a46388b1dcbb44db7e" integrity sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg== +readdirp@~3.6.0: + version "3.6.0" + resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7" + integrity sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA== + dependencies: + picomatch "^2.2.1" + require-directory@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" @@ -1968,7 +2504,7 @@ resolve.exports@^2.0.0: resolved "https://registry.yarnpkg.com/resolve.exports/-/resolve.exports-2.0.2.tgz#f8c934b8e6a13f539e38b7098e2e36134f01e800" integrity sha512-X2UW6Nw3n/aMgDVy+0rSqgHlv39WZAlZrXCdnbyEiKm17DSqHX4MmQMaST3FbeWR5FTuRcUwYAziZajji0Y7mg== -resolve@^1.20.0: +resolve@^1.0.0, resolve@^1.20.0: version "1.22.8" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.8.tgz#b6c87a9f2aa06dfab52e3d70ac8cde321fa5a48d" integrity sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw== @@ -1977,6 +2513,23 @@ resolve@^1.20.0: path-parse "^1.0.7" supports-preserve-symlinks-flag "^1.0.0" +rimraf@^2.6.1: + version "2.7.1" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec" + integrity sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w== + dependencies: + glob "^7.1.3" + +safe-buffer@5.2.1: + version "5.2.1" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" + integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== + +"safer-buffer@>= 2.1.2 < 3": + version "2.1.2" + resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" + integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== + semver@^6.3.0, semver@^6.3.1: version "6.3.1" resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" @@ -1987,6 +2540,52 @@ semver@^7.5.3, semver@^7.5.4: resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.2.tgz#1e3b34759f896e8f14d6134732ce798aeb0c6e13" integrity sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w== +send@0.18.0: + version "0.18.0" + resolved "https://registry.yarnpkg.com/send/-/send-0.18.0.tgz#670167cc654b05f5aa4a767f9113bb371bc706be" + integrity sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg== + dependencies: + debug "2.6.9" + depd "2.0.0" + destroy "1.2.0" + encodeurl "~1.0.2" + escape-html "~1.0.3" + etag "~1.8.1" + fresh "0.5.2" + http-errors "2.0.0" + mime "1.6.0" + ms "2.1.3" + on-finished "2.4.1" + range-parser "~1.2.1" + statuses "2.0.1" + +serve-static@1.15.0: + version "1.15.0" + resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.15.0.tgz#faaef08cffe0a1a62f60cad0c4e513cff0ac9540" + integrity sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g== + dependencies: + encodeurl "~1.0.2" + escape-html "~1.0.3" + parseurl "~1.3.3" + send "0.18.0" + +set-function-length@^1.2.1: + version "1.2.2" + resolved "https://registry.yarnpkg.com/set-function-length/-/set-function-length-1.2.2.tgz#aac72314198eaed975cf77b2c3b6b880695e5449" + integrity sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg== + dependencies: + define-data-property "^1.1.4" + es-errors "^1.3.0" + function-bind "^1.1.2" + get-intrinsic "^1.2.4" + gopd "^1.0.1" + has-property-descriptors "^1.0.2" + +setprototypeof@1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.2.0.tgz#66c9a24a73f9fc28cbe66b09fed3d33dcaf1b424" + integrity sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw== + shebang-command@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" @@ -1999,6 +2598,16 @@ shebang-regex@^3.0.0: resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== +side-channel@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.6.tgz#abd25fb7cd24baf45466406b1096b7831c9215f2" + integrity sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA== + dependencies: + call-bind "^1.0.7" + es-errors "^1.3.0" + get-intrinsic "^1.2.4" + object-inspect "^1.13.1" + signal-exit@^3.0.3, signal-exit@^3.0.7: version "3.0.7" resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" @@ -2022,6 +2631,14 @@ source-map-support@0.5.13: buffer-from "^1.0.0" source-map "^0.6.0" +source-map-support@^0.5.12: + version "0.5.21" + resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.21.tgz#04fe7c7f9e1ed2d662233c28cb2b35b9f63f6e4f" + integrity sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w== + dependencies: + buffer-from "^1.0.0" + source-map "^0.6.0" + source-map@^0.6.0, source-map@^0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" @@ -2039,6 +2656,11 @@ stack-utils@^2.0.3: dependencies: escape-string-regexp "^2.0.0" +statuses@2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/statuses/-/statuses-2.0.1.tgz#55cb000ccf1d48728bd23c685a063998cf1a1b63" + integrity sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ== + string-length@^4.0.1: version "4.0.2" resolved "https://registry.yarnpkg.com/string-length/-/string-length-4.0.2.tgz#a8a8dc7bd5c1a82b9b3c8b87e125f66871b6e57a" @@ -2078,6 +2700,11 @@ strip-final-newline@^2.0.0: resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad" integrity sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA== +strip-json-comments@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" + integrity sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ== + strip-json-comments@^3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" @@ -2135,6 +2762,16 @@ to-regex-range@^5.0.1: dependencies: is-number "^7.0.0" +toidentifier@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.1.tgz#3be34321a88a820ed1bd80dfaa33e479fbb8dd35" + integrity sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA== + +tree-kill@^1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/tree-kill/-/tree-kill-1.2.2.tgz#4ca09a9092c88b73a7cdc5e8a01b507b0790a0cc" + integrity sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A== + ts-jest@^29.1.4: version "29.1.4" resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-29.1.4.tgz#26f8a55ce31e4d2ef7a1fd47dc7fa127e92793ef" @@ -2149,7 +2786,23 @@ ts-jest@^29.1.4: semver "^7.5.3" yargs-parser "^21.0.1" -ts-node@^10.9.2: +ts-node-dev@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ts-node-dev/-/ts-node-dev-2.0.0.tgz#bdd53e17ab3b5d822ef519928dc6b4a7e0f13065" + integrity sha512-ywMrhCfH6M75yftYvrvNarLEY+SUXtUvU8/0Z6llrHQVBx12GiFk5sStF8UdfE/yfzk9IAq7O5EEbTQsxlBI8w== + dependencies: + chokidar "^3.5.1" + dynamic-dedupe "^0.3.0" + minimist "^1.2.6" + mkdirp "^1.0.4" + resolve "^1.0.0" + rimraf "^2.6.1" + source-map-support "^0.5.12" + tree-kill "^1.2.2" + ts-node "^10.4.0" + tsconfig "^7.0.0" + +ts-node@^10.4.0, ts-node@^10.9.2: version "10.9.2" resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-10.9.2.tgz#70f021c9e185bccdca820e26dc413805c101c71f" integrity sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ== @@ -2177,6 +2830,16 @@ tsconfig-paths@^4.2.0: minimist "^1.2.6" strip-bom "^3.0.0" +tsconfig@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/tsconfig/-/tsconfig-7.0.0.tgz#84538875a4dc216e5c4a5432b3a4dec3d54e91b7" + integrity sha512-vZXmzPrL+EmC4T/4rVlT2jNVMWCi/O4DIiSj3UHg1OE5kCKbk4mfrXc6dZksLgRM/TZlKnousKH9bbTazUWRRw== + dependencies: + "@types/strip-bom" "^3.0.0" + "@types/strip-json-comments" "0.0.30" + strip-bom "^3.0.0" + strip-json-comments "^2.0.0" + type-detect@4.0.8: version "4.0.8" resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c" @@ -2187,6 +2850,14 @@ type-fest@^0.21.3: resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.21.3.tgz#d260a24b0198436e133fa26a524a6d65fa3b2e37" integrity sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w== +type-is@~1.6.18: + version "1.6.18" + resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131" + integrity sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g== + dependencies: + media-typer "0.3.0" + mime-types "~2.1.24" + typescript@^5.4.5: version "5.4.5" resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.4.5.tgz#42ccef2c571fdbd0f6718b1d1f5e6e5ef006f611" @@ -2197,6 +2868,11 @@ undici-types@~5.26.4: resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-5.26.5.tgz#bcd539893d00b56e964fd2657a4866b221a65617" integrity sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA== +unpipe@1.0.0, unpipe@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" + integrity sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ== + update-browserslist-db@^1.0.13: version "1.0.16" resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.0.16.tgz#f6d489ed90fb2f07d67784eb3f53d7891f736356" @@ -2205,6 +2881,11 @@ update-browserslist-db@^1.0.13: escalade "^3.1.2" picocolors "^1.0.1" +utils-merge@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" + integrity sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA== + v8-compile-cache-lib@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz#6336e8d71965cb3d35a1bbb7868445a7c05264bf" @@ -2219,6 +2900,11 @@ v8-to-istanbul@^9.0.1: "@types/istanbul-lib-coverage" "^2.0.1" convert-source-map "^2.0.0" +vary@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" + integrity sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg== + walker@^1.0.8: version "1.0.8" resolved "https://registry.yarnpkg.com/walker/-/walker-1.0.8.tgz#bd498db477afe573dc04185f011d3ab8a8d7653f" @@ -2255,6 +2941,11 @@ write-file-atomic@^4.0.2: imurmurhash "^0.1.4" signal-exit "^3.0.7" +xtend@^4.0.0: + version "4.0.2" + resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" + integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ== + y18n@^5.0.5: version "5.0.8" resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55" From d8791ae802c2e28cc1114411d0f273cca8348d80 Mon Sep 17 00:00:00 2001 From: Leandro Reis Date: Mon, 10 Jun 2024 15:36:53 -0300 Subject: [PATCH 20/43] feat: add the Hapi HTTP server to serve the currency endpoints. --- package.json | 2 + src/infra/http-server/hapi-adapter.ts | 32 +++ src/infra/http-server/http-server.ts | 4 +- src/main/main-hapi.ts | 16 ++ .../{main-express.test.ts => api.test.ts} | 0 yarn.lock | 252 +++++++++++++++++- 6 files changed, 303 insertions(+), 3 deletions(-) create mode 100644 src/infra/http-server/hapi-adapter.ts create mode 100644 src/main/main-hapi.ts rename test/integration/{main-express.test.ts => api.test.ts} (100%) diff --git a/package.json b/package.json index fc6b60a32..256d5587f 100644 --- a/package.json +++ b/package.json @@ -8,10 +8,12 @@ "scripts": { "dev": "ts-node -r tsconfig-paths/register src/index.ts", "dev:express": "ts-node-dev -r tsconfig-paths/register src/main/main-express.ts", + "dev:hapi": "ts-node-dev -r tsconfig-paths/register src/main/main-hapi.ts", "test": "jest", "test:watch": "jest --watch --detectOpenHandles" }, "dependencies": { + "@hapi/hapi": "^21.3.9", "axios": "^1.7.2", "crypto": "^1.0.1", "currency.js": "^2.0.4", diff --git a/src/infra/http-server/hapi-adapter.ts b/src/infra/http-server/hapi-adapter.ts new file mode 100644 index 000000000..571da9cc4 --- /dev/null +++ b/src/infra/http-server/hapi-adapter.ts @@ -0,0 +1,32 @@ +import { Controller, HttpServer, Method } from '@/infra/http-server/http-server'; + +import Hapi from '@hapi/hapi'; + +export class HapiAdapter implements HttpServer { + private server: Hapi.Server; + + constructor() { + this.server = Hapi.server({}); + } + + register(method: Method, route: string, handler: Controller): void { + this.server.route({ + method, + path: route, + handler: async (req, res) => { + try { + const result = await handler({ query: req.query, params: req.params, body: req.payload }); + return result; + } catch (error: any) { + return res.response({ message: error.message }).code(500); + } + }, + }) + } + + + listen(port: number): void { + this.server.settings.port = port; + this.server.start(); + } +} diff --git a/src/infra/http-server/http-server.ts b/src/infra/http-server/http-server.ts index 0f51168a6..076e1423f 100644 --- a/src/infra/http-server/http-server.ts +++ b/src/infra/http-server/http-server.ts @@ -7,8 +7,8 @@ export type Method = 'get' | 'post' | 'put' | 'delete'; export type Controller = (args: ControllerParams) => Promise; -export type ControllerParams = { +export type ControllerParams = { query: { [key: string]: any } params: { [key: string]: string }, - body: { [key: string]: any } + body: T } \ No newline at end of file diff --git a/src/main/main-hapi.ts b/src/main/main-hapi.ts new file mode 100644 index 000000000..74953bc6d --- /dev/null +++ b/src/main/main-hapi.ts @@ -0,0 +1,16 @@ +import ConvertCurrency from "@/application/use-case/convert-currency"; +import CreateCurrencyRate from "@/application/use-case/create-currency-rate"; +import { DeleteCurrencyRateByCode } from "@/application/use-case/delete-currency-rate-by-code"; +import GetCurrencyRateByCode from "@/application/use-case/get-currency-rate-by-code"; +import { CurrencyController } from "@/infra/http-server/currency.controller"; +import { HapiAdapter } from "@/infra/http-server/hapi-adapter"; +import CurrencyRateRepositoryFake from "@/infra/repository/currency-rate-repository-fake"; + +const httpServer = new HapiAdapter(); +const currencyRateRepository = new CurrencyRateRepositoryFake(); +const currencyConverter = new ConvertCurrency(currencyRateRepository) +const createCurrencyRate = new CreateCurrencyRate(currencyRateRepository) +const getCurrencyRateByCode = new GetCurrencyRateByCode(currencyRateRepository) +const deleteCurrencyRateByCode = new DeleteCurrencyRateByCode(currencyRateRepository) +new CurrencyController(httpServer, currencyConverter, createCurrencyRate, getCurrencyRateByCode, deleteCurrencyRateByCode) +httpServer.listen(3000); \ No newline at end of file diff --git a/test/integration/main-express.test.ts b/test/integration/api.test.ts similarity index 100% rename from test/integration/main-express.test.ts rename to test/integration/api.test.ts diff --git a/yarn.lock b/yarn.lock index 6bf74fc67..cdff03cbf 100644 --- a/yarn.lock +++ b/yarn.lock @@ -304,6 +304,256 @@ dependencies: "@jridgewell/trace-mapping" "0.3.9" +"@hapi/accept@^6.0.1": + version "6.0.3" + resolved "https://registry.yarnpkg.com/@hapi/accept/-/accept-6.0.3.tgz#eef0800a4f89cd969da8e5d0311dc877c37279ab" + integrity sha512-p72f9k56EuF0n3MwlBNThyVE5PXX40g+aQh+C/xbKrfzahM2Oispv3AXmOIU51t3j77zay1qrX7IIziZXspMlw== + dependencies: + "@hapi/boom" "^10.0.1" + "@hapi/hoek" "^11.0.2" + +"@hapi/ammo@^6.0.1": + version "6.0.1" + resolved "https://registry.yarnpkg.com/@hapi/ammo/-/ammo-6.0.1.tgz#1bc9f7102724ff288ca03b721854fc5393ad123a" + integrity sha512-pmL+nPod4g58kXrMcsGLp05O2jF4P2Q3GiL8qYV7nKYEh3cGf+rV4P5Jyi2Uq0agGhVU63GtaSAfBEZOlrJn9w== + dependencies: + "@hapi/hoek" "^11.0.2" + +"@hapi/b64@^6.0.1": + version "6.0.1" + resolved "https://registry.yarnpkg.com/@hapi/b64/-/b64-6.0.1.tgz#786b47dc070e14465af49e2428c1025bd06ed3df" + integrity sha512-ZvjX4JQReUmBheeCq+S9YavcnMMHWqx3S0jHNXWIM1kQDxB9cyfSycpVvjfrKcIS8Mh5N3hmu/YKo4Iag9g2Kw== + dependencies: + "@hapi/hoek" "^11.0.2" + +"@hapi/boom@^10.0.0", "@hapi/boom@^10.0.1": + version "10.0.1" + resolved "https://registry.yarnpkg.com/@hapi/boom/-/boom-10.0.1.tgz#ebb14688275ae150aa6af788dbe482e6a6062685" + integrity sha512-ERcCZaEjdH3OgSJlyjVk8pHIFeus91CjKP3v+MpgBNp5IvGzP2l/bRiD78nqYcKPaZdbKkK5vDBVPd2ohHBlsA== + dependencies: + "@hapi/hoek" "^11.0.2" + +"@hapi/bounce@^3.0.1": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@hapi/bounce/-/bounce-3.0.1.tgz#25a51bf95733749c557c6bf948048bffa66435e4" + integrity sha512-G+/Pp9c1Ha4FDP+3Sy/Xwg2O4Ahaw3lIZFSX+BL4uWi64CmiETuZPxhKDUD4xBMOUZbBlzvO8HjiK8ePnhBadA== + dependencies: + "@hapi/boom" "^10.0.1" + "@hapi/hoek" "^11.0.2" + +"@hapi/bourne@^3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@hapi/bourne/-/bourne-3.0.0.tgz#f11fdf7dda62fe8e336fa7c6642d9041f30356d7" + integrity sha512-Waj1cwPXJDucOib4a3bAISsKJVb15MKi9IvmTI/7ssVEm6sywXGjVJDhl6/umt1pK1ZS7PacXU3A1PmFKHEZ2w== + +"@hapi/call@^9.0.1": + version "9.0.1" + resolved "https://registry.yarnpkg.com/@hapi/call/-/call-9.0.1.tgz#569b87d5b67abf0e58fb82a3894a61aaed3ca92e" + integrity sha512-uPojQRqEL1GRZR4xXPqcLMujQGaEpyVPRyBlD8Pp5rqgIwLhtveF9PkixiKru2THXvuN8mUrLeet5fqxKAAMGg== + dependencies: + "@hapi/boom" "^10.0.1" + "@hapi/hoek" "^11.0.2" + +"@hapi/catbox-memory@^6.0.1": + version "6.0.1" + resolved "https://registry.yarnpkg.com/@hapi/catbox-memory/-/catbox-memory-6.0.1.tgz#8f6b04c0cf2ce25da470324df360bd4e8d68b6ec" + integrity sha512-sVb+/ZxbZIvaMtJfAbdyY+QJUQg9oKTwamXpEg/5xnfG5WbJLTjvEn4kIGKz9pN3ENNbIL/bIdctmHmqi/AdGA== + dependencies: + "@hapi/boom" "^10.0.1" + "@hapi/hoek" "^11.0.2" + +"@hapi/catbox@^12.1.1": + version "12.1.1" + resolved "https://registry.yarnpkg.com/@hapi/catbox/-/catbox-12.1.1.tgz#9339dca0a5b18b3ca0a825ac5dfc916dbc5bab83" + integrity sha512-hDqYB1J+R0HtZg4iPH3LEnldoaBsar6bYp0EonBmNQ9t5CO+1CqgCul2ZtFveW1ReA5SQuze9GPSU7/aecERhw== + dependencies: + "@hapi/boom" "^10.0.1" + "@hapi/hoek" "^11.0.2" + "@hapi/podium" "^5.0.0" + "@hapi/validate" "^2.0.1" + +"@hapi/content@^6.0.0": + version "6.0.0" + resolved "https://registry.yarnpkg.com/@hapi/content/-/content-6.0.0.tgz#2427af3bac8a2f743512fce2a70cbdc365af29df" + integrity sha512-CEhs7j+H0iQffKfe5Htdak5LBOz/Qc8TRh51cF+BFv0qnuph3Em4pjGVzJMkI2gfTDdlJKWJISGWS1rK34POGA== + dependencies: + "@hapi/boom" "^10.0.0" + +"@hapi/cryptiles@^6.0.1": + version "6.0.1" + resolved "https://registry.yarnpkg.com/@hapi/cryptiles/-/cryptiles-6.0.1.tgz#7868a9d4233567ed66f0a9caf85fdcc56e980621" + integrity sha512-9GM9ECEHfR8lk5ASOKG4+4ZsEzFqLfhiryIJ2ISePVB92OHLp/yne4m+zn7z9dgvM98TLpiFebjDFQ0UHcqxXQ== + dependencies: + "@hapi/boom" "^10.0.1" + +"@hapi/file@^3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@hapi/file/-/file-3.0.0.tgz#f1fd824493ac89a6fceaf89c824afc5ae2121c09" + integrity sha512-w+lKW+yRrLhJu620jT3y+5g2mHqnKfepreykvdOcl9/6up8GrQQn+l3FRTsjHTKbkbfQFkuksHpdv2EcpKcJ4Q== + +"@hapi/hapi@^21.3.9": + version "21.3.9" + resolved "https://registry.yarnpkg.com/@hapi/hapi/-/hapi-21.3.9.tgz#953220abe26d12a7cbd308a57e434dedf0278cb3" + integrity sha512-AT5m+Rb8iSOFG3zWaiEuTJazf4HDYl5UpRpyxMJ3yR+g8tOEmqDv6FmXrLHShdvDOStAAepHGnr1G7egkFSRdw== + dependencies: + "@hapi/accept" "^6.0.1" + "@hapi/ammo" "^6.0.1" + "@hapi/boom" "^10.0.1" + "@hapi/bounce" "^3.0.1" + "@hapi/call" "^9.0.1" + "@hapi/catbox" "^12.1.1" + "@hapi/catbox-memory" "^6.0.1" + "@hapi/heavy" "^8.0.1" + "@hapi/hoek" "^11.0.2" + "@hapi/mimos" "^7.0.1" + "@hapi/podium" "^5.0.1" + "@hapi/shot" "^6.0.1" + "@hapi/somever" "^4.1.1" + "@hapi/statehood" "^8.1.1" + "@hapi/subtext" "^8.1.0" + "@hapi/teamwork" "^6.0.0" + "@hapi/topo" "^6.0.1" + "@hapi/validate" "^2.0.1" + +"@hapi/heavy@^8.0.1": + version "8.0.1" + resolved "https://registry.yarnpkg.com/@hapi/heavy/-/heavy-8.0.1.tgz#e2be4a6a249005b5a587f7604aafa8ed02461fb6" + integrity sha512-gBD/NANosNCOp6RsYTsjo2vhr5eYA3BEuogk6cxY0QdhllkkTaJFYtTXv46xd6qhBVMbMMqcSdtqey+UQU3//w== + dependencies: + "@hapi/boom" "^10.0.1" + "@hapi/hoek" "^11.0.2" + "@hapi/validate" "^2.0.1" + +"@hapi/hoek@^11.0.2": + version "11.0.4" + resolved "https://registry.yarnpkg.com/@hapi/hoek/-/hoek-11.0.4.tgz#42a7f244fd3dd777792bfb74b8c6340ae9182f37" + integrity sha512-PnsP5d4q7289pS2T2EgGz147BFJ2Jpb4yrEdkpz2IhgEUzos1S7HTl7ezWh1yfYzYlj89KzLdCRkqsP6SIryeQ== + +"@hapi/iron@^7.0.1": + version "7.0.1" + resolved "https://registry.yarnpkg.com/@hapi/iron/-/iron-7.0.1.tgz#f74bace8dad9340c7c012c27c078504f070f14b5" + integrity sha512-tEZnrOujKpS6jLKliyWBl3A9PaE+ppuL/+gkbyPPDb/l2KSKQyH4lhMkVb+sBhwN+qaxxlig01JRqB8dk/mPxQ== + dependencies: + "@hapi/b64" "^6.0.1" + "@hapi/boom" "^10.0.1" + "@hapi/bourne" "^3.0.0" + "@hapi/cryptiles" "^6.0.1" + "@hapi/hoek" "^11.0.2" + +"@hapi/mimos@^7.0.1": + version "7.0.1" + resolved "https://registry.yarnpkg.com/@hapi/mimos/-/mimos-7.0.1.tgz#5b65c76bb9da28ba34b0092215891f2c72bc899d" + integrity sha512-b79V+BrG0gJ9zcRx1VGcCI6r6GEzzZUgiGEJVoq5gwzuB2Ig9Cax8dUuBauQCFKvl2YWSWyOc8mZ8HDaJOtkew== + dependencies: + "@hapi/hoek" "^11.0.2" + mime-db "^1.52.0" + +"@hapi/nigel@^5.0.1": + version "5.0.1" + resolved "https://registry.yarnpkg.com/@hapi/nigel/-/nigel-5.0.1.tgz#a6dfe357e9d48d944e2ffc552bd95cb701d79ee9" + integrity sha512-uv3dtYuB4IsNaha+tigWmN8mQw/O9Qzl5U26Gm4ZcJVtDdB1AVJOwX3X5wOX+A07qzpEZnOMBAm8jjSqGsU6Nw== + dependencies: + "@hapi/hoek" "^11.0.2" + "@hapi/vise" "^5.0.1" + +"@hapi/pez@^6.1.0": + version "6.1.0" + resolved "https://registry.yarnpkg.com/@hapi/pez/-/pez-6.1.0.tgz#64d9f95580fc7d8f1d13437ee4a8676709954fda" + integrity sha512-+FE3sFPYuXCpuVeHQ/Qag1b45clR2o54QoonE/gKHv9gukxQ8oJJZPR7o3/ydDTK6racnCJXxOyT1T93FCJMIg== + dependencies: + "@hapi/b64" "^6.0.1" + "@hapi/boom" "^10.0.1" + "@hapi/content" "^6.0.0" + "@hapi/hoek" "^11.0.2" + "@hapi/nigel" "^5.0.1" + +"@hapi/podium@^5.0.0", "@hapi/podium@^5.0.1": + version "5.0.1" + resolved "https://registry.yarnpkg.com/@hapi/podium/-/podium-5.0.1.tgz#f292b4c0ca3118747394a102c6c3340bda96662f" + integrity sha512-eznFTw6rdBhAijXFIlBOMJJd+lXTvqbrBIS4Iu80r2KTVIo4g+7fLy4NKp/8+UnSt5Ox6mJtAlKBU/Sf5080TQ== + dependencies: + "@hapi/hoek" "^11.0.2" + "@hapi/teamwork" "^6.0.0" + "@hapi/validate" "^2.0.1" + +"@hapi/shot@^6.0.1": + version "6.0.1" + resolved "https://registry.yarnpkg.com/@hapi/shot/-/shot-6.0.1.tgz#ea84d1810b7c8599d5517c23b4ec55a529d7dc16" + integrity sha512-s5ynMKZXYoDd3dqPw5YTvOR/vjHvMTxc388+0qL0jZZP1+uwXuUD32o9DuuuLsmTlyXCWi02BJl1pBpwRuUrNA== + dependencies: + "@hapi/hoek" "^11.0.2" + "@hapi/validate" "^2.0.1" + +"@hapi/somever@^4.1.1": + version "4.1.1" + resolved "https://registry.yarnpkg.com/@hapi/somever/-/somever-4.1.1.tgz#b492c78408303c72cd1a39c5060f35d18a404b27" + integrity sha512-lt3QQiDDOVRatS0ionFDNrDIv4eXz58IibQaZQDOg4DqqdNme8oa0iPWcE0+hkq/KTeBCPtEOjDOBKBKwDumVg== + dependencies: + "@hapi/bounce" "^3.0.1" + "@hapi/hoek" "^11.0.2" + +"@hapi/statehood@^8.1.1": + version "8.1.1" + resolved "https://registry.yarnpkg.com/@hapi/statehood/-/statehood-8.1.1.tgz#db4bd14c90810a1389763cb0b0b8f221aa4179c1" + integrity sha512-YbK7PSVUA59NArAW5Np0tKRoIZ5VNYUicOk7uJmWZF6XyH5gGL+k62w77SIJb0AoAJ0QdGQMCQ/WOGL1S3Ydow== + dependencies: + "@hapi/boom" "^10.0.1" + "@hapi/bounce" "^3.0.1" + "@hapi/bourne" "^3.0.0" + "@hapi/cryptiles" "^6.0.1" + "@hapi/hoek" "^11.0.2" + "@hapi/iron" "^7.0.1" + "@hapi/validate" "^2.0.1" + +"@hapi/subtext@^8.1.0": + version "8.1.0" + resolved "https://registry.yarnpkg.com/@hapi/subtext/-/subtext-8.1.0.tgz#58733020a6655bc4d978df9e2f75e31696ff3f91" + integrity sha512-PyaN4oSMtqPjjVxLny1k0iYg4+fwGusIhaom9B2StinBclHs7v46mIW706Y+Wo21lcgulGyXbQrmT/w4dus6ww== + dependencies: + "@hapi/boom" "^10.0.1" + "@hapi/bourne" "^3.0.0" + "@hapi/content" "^6.0.0" + "@hapi/file" "^3.0.0" + "@hapi/hoek" "^11.0.2" + "@hapi/pez" "^6.1.0" + "@hapi/wreck" "^18.0.1" + +"@hapi/teamwork@^6.0.0": + version "6.0.0" + resolved "https://registry.yarnpkg.com/@hapi/teamwork/-/teamwork-6.0.0.tgz#b3a173cf811ba59fc6ee22318a1b51f4561f06e0" + integrity sha512-05HumSy3LWfXpmJ9cr6HzwhAavrHkJ1ZRCmNE2qJMihdM5YcWreWPfyN0yKT2ZjCM92au3ZkuodjBxOibxM67A== + +"@hapi/topo@^6.0.1": + version "6.0.2" + resolved "https://registry.yarnpkg.com/@hapi/topo/-/topo-6.0.2.tgz#f219c1c60da8430228af4c1f2e40c32a0d84bbb4" + integrity sha512-KR3rD5inZbGMrHmgPxsJ9dbi6zEK+C3ZwUwTa+eMwWLz7oijWUTWD2pMSNNYJAU6Qq+65NkxXjqHr/7LM2Xkqg== + dependencies: + "@hapi/hoek" "^11.0.2" + +"@hapi/validate@^2.0.1": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@hapi/validate/-/validate-2.0.1.tgz#45cf228c4c8cfc61ba2da7e0a5ba93ff3b9afff1" + integrity sha512-NZmXRnrSLK8MQ9y/CMqE9WSspgB9xA41/LlYR0k967aSZebWr4yNrpxIbov12ICwKy4APSlWXZga9jN5p6puPA== + dependencies: + "@hapi/hoek" "^11.0.2" + "@hapi/topo" "^6.0.1" + +"@hapi/vise@^5.0.1": + version "5.0.1" + resolved "https://registry.yarnpkg.com/@hapi/vise/-/vise-5.0.1.tgz#5c9f16bcf1c039ddd4b6cad5f32d71eeb6bb7dac" + integrity sha512-XZYWzzRtINQLedPYlIkSkUr7m5Ddwlu99V9elh8CSygXstfv3UnWIXT0QD+wmR0VAG34d2Vx3olqcEhRRoTu9A== + dependencies: + "@hapi/hoek" "^11.0.2" + +"@hapi/wreck@^18.0.1": + version "18.1.0" + resolved "https://registry.yarnpkg.com/@hapi/wreck/-/wreck-18.1.0.tgz#68e631fc7568ebefc6252d5b86cb804466c8dbe6" + integrity sha512-0z6ZRCmFEfV/MQqkQomJ7sl/hyxvcZM7LtuVqN3vdAO4vM9eBbowl0kaqQj9EJJQab+3Uuh1GxbGIBFy4NfJ4w== + dependencies: + "@hapi/boom" "^10.0.1" + "@hapi/bourne" "^3.0.0" + "@hapi/hoek" "^11.0.2" + "@istanbuljs/load-nyc-config@^1.0.0": version "1.1.0" resolved "https://registry.yarnpkg.com/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz#fd3db1d59ecf7cf121e80650bb86712f9b55eced" @@ -2213,7 +2463,7 @@ micromatch@^4.0.4: braces "^3.0.3" picomatch "^2.3.1" -mime-db@1.52.0: +mime-db@1.52.0, mime-db@^1.52.0: version "1.52.0" resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== From 44c19fbc88985bfca50b36fbf5c61eed0bbf491e Mon Sep 17 00:00:00 2001 From: Leandro Reis Date: Tue, 11 Jun 2024 19:28:32 -0300 Subject: [PATCH 21/43] feat: Integrate PostgreSQL database with Docker support - Added PostgreSQL database setup to the application using Docker - Configured docker-compose.yml for PostgreSQL service - Created initialization scripts for database schema and seed data --- docker-compose.yml | 12 + initdb/init.sql | 402 ++++++++++++++++++ package.json | 4 +- .../repository/currency-rate-repository.ts | 2 +- src/infra/database/database-connection.ts | 4 + src/infra/database/pgpromise-adapter.ts | 20 + .../currency-rate-repository-database.ts | 60 +++ src/main/main-express-pg.ts | 18 + yarn.lock | 108 +++++ 9 files changed, 628 insertions(+), 2 deletions(-) create mode 100644 docker-compose.yml create mode 100644 initdb/init.sql create mode 100644 src/infra/database/database-connection.ts create mode 100644 src/infra/database/pgpromise-adapter.ts create mode 100644 src/infra/repository/currency-rate-repository-database.ts create mode 100644 src/main/main-express-pg.ts diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 000000000..ba77c7a13 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,12 @@ +version: "3.9" + +services: + db: + image: postgres + ports: + - "5432:5432" + environment: + POSTGRES_USER: postgres + POSTGRES_PASSWORD: 123456 + volumes: + - ./initdb:/docker-entrypoint-initdb.d diff --git a/initdb/init.sql b/initdb/init.sql new file mode 100644 index 000000000..f75150455 --- /dev/null +++ b/initdb/init.sql @@ -0,0 +1,402 @@ +-- Drop the rate table if it exists to apply new configuration +-- DROP TABLE IF EXISTS currency; +-- DROP TABLE IF EXISTS rate; + +-- Create the currency table if it does not exist +CREATE TABLE IF NOT EXISTS currency ( + id UUID DEFAULT gen_random_uuid() PRIMARY KEY, + code VARCHAR(10) NOT NULL UNIQUE, + name VARCHAR(50) NOT NULL, + symbol VARCHAR(10) NOT NULL, + decimal_digits INTEGER NOT NULL +); + +-- Create the rate table if it does not exist +CREATE TABLE IF NOT EXISTS rate ( + id UUID DEFAULT gen_random_uuid() PRIMARY KEY, + currency_id UUID NOT NULL REFERENCES currency(id) ON DELETE CASCADE, + rate NUMERIC(18, 10) NOT NULL, + updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), + UNIQUE(currency_id, updated_at) +); + +-- Insert currencies into the currency table +-- These inserts will not execute if the currency code already exists + +INSERT INTO currency (code, name, symbol, decimal_digits) VALUES ('AED', 'United Arab Emirates Dirham', 'AED', 2) ON CONFLICT (code) DO NOTHING; +INSERT INTO currency (code, name, symbol, decimal_digits) VALUES ('AFN', 'Afghan Afghani', 'Af', 0) ON CONFLICT (code) DO NOTHING; +INSERT INTO currency (code, name, symbol, decimal_digits) VALUES ('ALL', 'Albanian Lek', 'ALL', 0) ON CONFLICT (code) DO NOTHING; +INSERT INTO currency (code, name, symbol, decimal_digits) VALUES ('AMD', 'Armenian Dram', 'AMD', 0) ON CONFLICT (code) DO NOTHING; +INSERT INTO currency (code, name, symbol, decimal_digits) VALUES ('ANG', 'NL Antillean Guilder', 'ƒ', 2) ON CONFLICT (code) DO NOTHING; +INSERT INTO currency (code, name, symbol, decimal_digits) VALUES ('AOA', 'Angolan Kwanza', 'Kz', 2) ON CONFLICT (code) DO NOTHING; +INSERT INTO currency (code, name, symbol, decimal_digits) VALUES ('ARS', 'Argentine Peso', 'AR$', 2) ON CONFLICT (code) DO NOTHING; +INSERT INTO currency (code, name, symbol, decimal_digits) VALUES ('AUD', 'Australian Dollar', 'AU$', 2) ON CONFLICT (code) DO NOTHING; +INSERT INTO currency (code, name, symbol, decimal_digits) VALUES ('AWG', 'Aruban Florin', 'Afl.', 2) ON CONFLICT (code) DO NOTHING; +INSERT INTO currency (code, name, symbol, decimal_digits) VALUES ('AZN', 'Azerbaijani Manat', 'man.', 2) ON CONFLICT (code) DO NOTHING; +INSERT INTO currency (code, name, symbol, decimal_digits) VALUES ('BAM', 'Bosnia-Herzegovina Convertible Mark', 'KM', 2) ON CONFLICT (code) DO NOTHING; +INSERT INTO currency (code, name, symbol, decimal_digits) VALUES ('BBD', 'Barbadian Dollar', 'Bds$', 2) ON CONFLICT (code) DO NOTHING; +INSERT INTO currency (code, name, symbol, decimal_digits) VALUES ('BDT', 'Bangladeshi Taka', 'Tk', 2) ON CONFLICT (code) DO NOTHING; +INSERT INTO currency (code, name, symbol, decimal_digits) VALUES ('BGN', 'Bulgarian Lev', 'BGN', 2) ON CONFLICT (code) DO NOTHING; +INSERT INTO currency (code, name, symbol, decimal_digits) VALUES ('BHD', 'Bahraini Dinar', 'BD',3) ON CONFLICT (code) DO NOTHING; +INSERT INTO currency (code, name, symbol, decimal_digits) VALUES ('BIF', 'Burundian Franc', 'FBu', 0) ON CONFLICT (code) DO NOTHING; +INSERT INTO currency (code, name, symbol, decimal_digits) VALUES ('BMD', 'Bermudan Dollar', 'BD$', 2) ON CONFLICT (code) DO NOTHING; +INSERT INTO currency (code, name, symbol, decimal_digits) VALUES ('BND', 'Brunei Dollar', 'BN$', 2) ON CONFLICT (code) DO NOTHING; +INSERT INTO currency (code, name, symbol, decimal_digits) VALUES ('BOB', 'Bolivian Boliviano', 'Bs', 2) ON CONFLICT (code) DO NOTHING; +INSERT INTO currency (code, name, symbol, decimal_digits) VALUES ('BRL', 'Brazilian Real', 'R$', 2) ON CONFLICT (code) DO NOTHING; +INSERT INTO currency (code, name, symbol, decimal_digits) VALUES ('BSD', 'Bahamian Dollar', 'B$', 2) ON CONFLICT (code) DO NOTHING; +INSERT INTO currency (code, name, symbol, decimal_digits) VALUES ('BTN', 'Bhutanese Ngultrum', 'Nu.', 2) ON CONFLICT (code) DO NOTHING; +INSERT INTO currency (code, name, symbol, decimal_digits) VALUES ('BWP', 'Botswanan Pula', 'BWP', 2) ON CONFLICT (code) DO NOTHING; +INSERT INTO currency (code, name, symbol, decimal_digits) VALUES ('BYN', 'Belarusian ruble', 'Br', 2) ON CONFLICT (code) DO NOTHING; +INSERT INTO currency (code, name, symbol, decimal_digits) VALUES ('BYR', 'Belarusian Ruble', 'BYR', 0) ON CONFLICT (code) DO NOTHING; +INSERT INTO currency (code, name, symbol, decimal_digits) VALUES ('BZD', 'Belize Dollar', 'BZ$', 2) ON CONFLICT (code) DO NOTHING; +INSERT INTO currency (code, name, symbol, decimal_digits) VALUES ('CAD', 'Canadian Dollar', 'CA$', 2) ON CONFLICT (code) DO NOTHING; +INSERT INTO currency (code, name, symbol, decimal_digits) VALUES ('CDF', 'Congolese Franc', 'CDF', 2) ON CONFLICT (code) DO NOTHING; +INSERT INTO currency (code, name, symbol, decimal_digits) VALUES ('CHF', 'Swiss Franc', 'CHF', 2) ON CONFLICT (code) DO NOTHING; +INSERT INTO currency (code, name, symbol, decimal_digits) VALUES ('CLF', 'Unidad de Fomento', 'UF', 2) ON CONFLICT (code) DO NOTHING; +INSERT INTO currency (code, name, symbol, decimal_digits) VALUES ('CLP', 'Chilean Peso', 'CL$', 0) ON CONFLICT (code) DO NOTHING; +INSERT INTO currency (code, name, symbol, decimal_digits) VALUES ('CNY', 'Chinese Yuan', 'CN¥', 2) ON CONFLICT (code) DO NOTHING; +INSERT INTO currency (code, name, symbol, decimal_digits) VALUES ('COP', 'Coombian Peso', 'CO$', 0) ON CONFLICT (code) DO NOTHING; +INSERT INTO currency (code, name, symbol, decimal_digits) VALUES ('CRC', 'Costa Rican Colón', '₡', 0) ON CONFLICT (code) DO NOTHING; +INSERT INTO currency (code, name, symbol, decimal_digits) VALUES ('CUC', 'Cuban Convertible Peso', 'CUC$', 2) ON CONFLICT (code) DO NOTHING; +INSERT INTO currency (code, name, symbol, decimal_digits) VALUES ('CUP', 'Cuban Peso', '$MN', 2) ON CONFLICT (code) DO NOTHING; +INSERT INTO currency (code, name, symbol, decimal_digits) VALUES ('CVE', 'Cape Verdean Escudo', 'CV$', 2) ON CONFLICT (code) DO NOTHING; +INSERT INTO currency (code, name, symbol, decimal_digits) VALUES ('CZK', 'Czech Republic Koruna', 'Kč', 2) ON CONFLICT (code) DO NOTHING; +INSERT INTO currency (code, name, symbol, decimal_digits) VALUES ('DJF', 'Djiboutian Franc', 'Fdj', 0) ON CONFLICT (code) DO NOTHING; +INSERT INTO currency (code, name, symbol, decimal_digits) VALUES ('DKK', 'Danish Krone', 'Dkr', 2) ON CONFLICT (code) DO NOTHING; +INSERT INTO currency (code, name, symbol, decimal_digits) VALUES ('DOP', 'Dominican Peso', 'RD$', 2) ON CONFLICT (code) DO NOTHING; +INSERT INTO currency (code, name, symbol, decimal_digits) VALUES ('DZD', 'Algerian Dinar', 'DA', 2) ON CONFLICT (code) DO NOTHING; +INSERT INTO currency (code, name, symbol, decimal_digits) VALUES ('EGP', 'Egyptian Pound', 'EGP', 2) ON CONFLICT (code) DO NOTHING; +INSERT INTO currency (code, name, symbol, decimal_digits) VALUES ('ERN', 'Eritrean Nakfa', 'Nfk', 2) ON CONFLICT (code) DO NOTHING; +INSERT INTO currency (code, name, symbol, decimal_digits) VALUES ('ETB', 'Ethiopian Birr', 'Br', 2) ON CONFLICT (code) DO NOTHING; +INSERT INTO currency (code, name, symbol, decimal_digits) VALUES ('EUR', 'Euro', '€', 2) ON CONFLICT (code) DO NOTHING; +INSERT INTO currency (code, name, symbol, decimal_digits) VALUES ('FJD', 'Fijian Dollar', 'FJ$', 2) ON CONFLICT (code) DO NOTHING; +INSERT INTO currency (code, name, symbol, decimal_digits) VALUES ('FKP', 'Falkland Islands Pound', 'FK£', 2) ON CONFLICT (code) DO NOTHING; +INSERT INTO currency (code, name, symbol, decimal_digits) VALUES ('GBP', 'British Pound Sterling', '£', 2) ON CONFLICT (code) DO NOTHING; +INSERT INTO currency (code, name, symbol, decimal_digits) VALUES ('GEL', 'Georgian Lari', 'GEL', 2) ON CONFLICT (code) DO NOTHING; +INSERT INTO currency (code, name, symbol, decimal_digits) VALUES ('GGP', 'Guernsey pound', '£', 2) ON CONFLICT (code) DO NOTHING; +INSERT INTO currency (code, name, symbol, decimal_digits) VALUES ('GHS', 'Ghanaian Cedi', 'GH₵', 2) ON CONFLICT (code) DO NOTHING; +INSERT INTO currency (code, name, symbol, decimal_digits) VALUES ('GIP', 'Gibraltar Pound', '£', 2) ON CONFLICT (code) DO NOTHING; +INSERT INTO currency (code, name, symbol, decimal_digits) VALUES ('GMD', 'Gambian Dalasi', 'D', 2) ON CONFLICT (code) DO NOTHING; +INSERT INTO currency (code, name, symbol, decimal_digits) VALUES ('GNF', 'Guinean Franc', 'FG', 0) ON CONFLICT (code) DO NOTHING; +INSERT INTO currency (code, name, symbol, decimal_digits) VALUES ('GTQ', 'Guatemalan Quetzal', 'GTQ', 2) ON CONFLICT (code) DO NOTHING; +INSERT INTO currency (code, name, symbol, decimal_digits) VALUES ('GYD', 'Guyanaese Dollar', 'G$', 2) ON CONFLICT (code) DO NOTHING; +INSERT INTO currency (code, name, symbol, decimal_digits) VALUES ('HKD', 'Hong Kong Dollar', 'HK$', 2) ON CONFLICT (code) DO NOTHING; +INSERT INTO currency (code, name, symbol, decimal_digits) VALUES ('HNL', 'Honduran Lempira', 'HNL', 2) ON CONFLICT (code) DO NOTHING; +INSERT INTO currency (code, name, symbol, decimal_digits) VALUES ('HRK', 'Croatian Kuna', 'kn', 2) ON CONFLICT (code) DO NOTHING; +INSERT INTO currency (code, name, symbol, decimal_digits) VALUES ('HTG', 'Haitian Gourde', 'G', 2) ON CONFLICT (code) DO NOTHING; +INSERT INTO currency (code, name, symbol, decimal_digits) VALUES ('HUF', 'Hungarian Forint', 'Ft', 0) ON CONFLICT (code) DO NOTHING; +INSERT INTO currency (code, name, symbol, decimal_digits) VALUES ('IDR', 'Indonesian Rupiah', 'Rp', 0) ON CONFLICT (code) DO NOTHING; +INSERT INTO currency (code, name, symbol, decimal_digits) VALUES ('ILS', 'Israeli New Sheqel', '₪', 2) ON CONFLICT (code) DO NOTHING; +INSERT INTO currency (code, name, symbol, decimal_digits) VALUES ('IMP', 'Manx pound', '£', 2) ON CONFLICT (code) DO NOTHING; +INSERT INTO currency (code, name, symbol, decimal_digits) VALUES ('INR', 'Indian Rupee', 'Rs', 2) ON CONFLICT (code) DO NOTHING; +INSERT INTO currency (code, name, symbol, decimal_digits) VALUES ('IQD', 'Iraqi Dinar', 'IQD', 0) ON CONFLICT (code) DO NOTHING; +INSERT INTO currency (code, name, symbol, decimal_digits) VALUES ('IRR', 'Iranian Rial', 'IRR', 0) ON CONFLICT (code) DO NOTHING; +INSERT INTO currency (code, name, symbol, decimal_digits) VALUES ('ISK', 'Icelandic Króna', 'Ikr', 0) ON CONFLICT (code) DO NOTHING; +INSERT INTO currency (code, name, symbol, decimal_digits) VALUES ('JEP', 'Jersey pound', '£', 2) ON CONFLICT (code) DO NOTHING; +INSERT INTO currency (code, name, symbol, decimal_digits) VALUES ('JMD', 'Jamaican Dollar', 'J$', 2) ON CONFLICT (code) DO NOTHING; +INSERT INTO currency (code, name, symbol, decimal_digits) VALUES ('JOD', 'Jordanian Dinar', 'JD',3) ON CONFLICT (code) DO NOTHING; +INSERT INTO currency (code, name, symbol, decimal_digits) VALUES ('JPY', 'Japanese Yen', '¥', 0) ON CONFLICT (code) DO NOTHING; +INSERT INTO currency (code, name, symbol, decimal_digits) VALUES ('KES', 'Kenyan Shilling', 'Ksh', 2) ON CONFLICT (code) DO NOTHING; +INSERT INTO currency (code, name, symbol, decimal_digits) VALUES ('KGS', 'Kyrgystani Som', 'KGS', 2) ON CONFLICT (code) DO NOTHING; +INSERT INTO currency (code, name, symbol, decimal_digits) VALUES ('KHR', 'Cambodian Riel', 'KHR', 2) ON CONFLICT (code) DO NOTHING; +INSERT INTO currency (code, name, symbol, decimal_digits) VALUES ('KMF', 'Comorian Franc', 'CF', 0) ON CONFLICT (code) DO NOTHING; +INSERT INTO currency (code, name, symbol, decimal_digits) VALUES ('KPW', 'North Korean Won', '₩', 2) ON CONFLICT (code) DO NOTHING; +INSERT INTO currency (code, name, symbol, decimal_digits) VALUES ('KRW', 'South Korean Won', '₩', 0) ON CONFLICT (code) DO NOTHING; +INSERT INTO currency (code, name, symbol, decimal_digits) VALUES ('KWD', 'Kuwaiti Dinar', 'KD',3) ON CONFLICT (code) DO NOTHING; +INSERT INTO currency (code, name, symbol, decimal_digits) VALUES ('KYD', 'Cayman Islands Dollar', 'CI$', 2) ON CONFLICT (code) DO NOTHING; +INSERT INTO currency (code, name, symbol, decimal_digits) VALUES ('KZT', 'Kazakhstani Tenge', 'KZT', 2) ON CONFLICT (code) DO NOTHING; +INSERT INTO currency (code, name, symbol, decimal_digits) VALUES ('LAK', 'Laotian Kip', '₭N', 0) ON CONFLICT (code) DO NOTHING; +INSERT INTO currency (code, name, symbol, decimal_digits) VALUES ('LBP', 'Lebanese Pound', 'LB£', 0) ON CONFLICT (code) DO NOTHING; +INSERT INTO currency (code, name, symbol, decimal_digits) VALUES ('LKR', 'Sri Lankan Rupee', 'SLRs', 2) ON CONFLICT (code) DO NOTHING; +INSERT INTO currency (code, name, symbol, decimal_digits) VALUES ('LRD', 'Liberian Dollar', 'LD$', 2) ON CONFLICT (code) DO NOTHING; +INSERT INTO currency (code, name, symbol, decimal_digits) VALUES ('LSL', 'Lesotho Loti', 'L', 2) ON CONFLICT (code) DO NOTHING; +INSERT INTO currency (code, name, symbol, decimal_digits) VALUES ('LTL', 'Lithuanian Litas', 'Lt', 2) ON CONFLICT (code) DO NOTHING; +INSERT INTO currency (code, name, symbol, decimal_digits) VALUES ('LVL', 'Latvian Lats', 'Ls', 2) ON CONFLICT (code) DO NOTHING; +INSERT INTO currency (code, name, symbol, decimal_digits) VALUES ('LYD', 'Libyan Dinar', 'LD',3) ON CONFLICT (code) DO NOTHING; +INSERT INTO currency (code, name, symbol, decimal_digits) VALUES ('MAD', 'Moroccan Dirham', 'MAD', 2) ON CONFLICT (code) DO NOTHING; +INSERT INTO currency (code, name, symbol, decimal_digits) VALUES ('MDL', 'Moldovan Leu', 'MDL', 2) ON CONFLICT (code) DO NOTHING; +INSERT INTO currency (code, name, symbol, decimal_digits) VALUES ('MGA', 'Malagasy Ariary', 'MGA', 0) ON CONFLICT (code) DO NOTHING; +INSERT INTO currency (code, name, symbol, decimal_digits) VALUES ('MKD', 'Macedonian Denar', 'MKD', 2) ON CONFLICT (code) DO NOTHING; +INSERT INTO currency (code, name, symbol, decimal_digits) VALUES ('MMK', 'Myanma Kyat', 'MMK', 0) ON CONFLICT (code) DO NOTHING; +INSERT INTO currency (code, name, symbol, decimal_digits) VALUES ('MNT', 'Mongolian Tugrik', '₮', 2) ON CONFLICT (code) DO NOTHING; +INSERT INTO currency (code, name, symbol, decimal_digits) VALUES ('MOP', 'Macanese Pataca', 'MOP$', 2) ON CONFLICT (code) DO NOTHING; +INSERT INTO currency (code, name, symbol, decimal_digits) VALUES ('MRO', 'Mauritanian ouguiya', 'UM', 2) ON CONFLICT (code) DO NOTHING; +INSERT INTO currency (code, name, symbol, decimal_digits) VALUES ('MUR', 'Mauritian Rupee', 'MURs', 0) ON CONFLICT (code) DO NOTHING; +INSERT INTO currency (code, name, symbol, decimal_digits) VALUES ('MVR', 'Maldivian Rufiyaa', 'MRf', 2) ON CONFLICT (code) DO NOTHING; +INSERT INTO currency (code, name, symbol, decimal_digits) VALUES ('MWK', 'Malawian Kwacha', 'MK', 2) ON CONFLICT (code) DO NOTHING; +INSERT INTO currency (code, name, symbol, decimal_digits) VALUES ('MXN', 'Mexican Peso', 'MX$', 2) ON CONFLICT (code) DO NOTHING; +INSERT INTO currency (code, name, symbol, decimal_digits) VALUES ('MYR', 'Malaysian Ringgit', 'RM', 2) ON CONFLICT (code) DO NOTHING; +INSERT INTO currency (code, name, symbol, decimal_digits) VALUES ('MZN', 'Mozambican Metical', 'MTn', 2) ON CONFLICT (code) DO NOTHING; +INSERT INTO currency (code, name, symbol, decimal_digits) VALUES ('NAD', 'Namibian Dollar', 'N$', 2) ON CONFLICT (code) DO NOTHING; +INSERT INTO currency (code, name, symbol, decimal_digits) VALUES ('NGN', 'Nigerian Naira', '₦', 2) ON CONFLICT (code) DO NOTHING; +INSERT INTO currency (code, name, symbol, decimal_digits) VALUES ('NIO', 'Nicaraguan Córdoba', 'C$', 2) ON CONFLICT (code) DO NOTHING; +INSERT INTO currency (code, name, symbol, decimal_digits) VALUES ('NOK', 'Norwegian Krone', 'Nkr', 2) ON CONFLICT (code) DO NOTHING; +INSERT INTO currency (code, name, symbol, decimal_digits) VALUES ('NPR', 'Nepalese Rupee', 'NPRs', 2) ON CONFLICT (code) DO NOTHING; +INSERT INTO currency (code, name, symbol, decimal_digits) VALUES ('NZD', 'New Zealand Dollar', 'NZ$', 2) ON CONFLICT (code) DO NOTHING; +INSERT INTO currency (code, name, symbol, decimal_digits) VALUES ('OMR', 'Omani Rial', 'OMR',3) ON CONFLICT (code) DO NOTHING; +INSERT INTO currency (code, name, symbol, decimal_digits) VALUES ('PAB', 'Panamanian Balboa', 'B/.', 2) ON CONFLICT (code) DO NOTHING; +INSERT INTO currency (code, name, symbol, decimal_digits) VALUES ('PEN', 'Peruvian Nuevo Sol', 'S/.', 2) ON CONFLICT (code) DO NOTHING; +INSERT INTO currency (code, name, symbol, decimal_digits) VALUES ('PGK', 'Papua New Guinean Kina', 'K', 2) ON CONFLICT (code) DO NOTHING; +INSERT INTO currency (code, name, symbol, decimal_digits) VALUES ('PHP', 'Philippine Peso', '₱', 2) ON CONFLICT (code) DO NOTHING; +INSERT INTO currency (code, name, symbol, decimal_digits) VALUES ('PKR', 'Pakistani Rupee', 'PKRs', 0) ON CONFLICT (code) DO NOTHING; +INSERT INTO currency (code, name, symbol, decimal_digits) VALUES ('PLN', 'Polish Zloty', 'zł', 2) ON CONFLICT (code) DO NOTHING; +INSERT INTO currency (code, name, symbol, decimal_digits) VALUES ('PYG', 'Paraguayan Guarani', '₲', 0) ON CONFLICT (code) DO NOTHING; +INSERT INTO currency (code, name, symbol, decimal_digits) VALUES ('QAR', 'Qatari Rial', 'QR', 2) ON CONFLICT (code) DO NOTHING; +INSERT INTO currency (code, name, symbol, decimal_digits) VALUES ('RON', 'Romanian Leu', 'RON', 2) ON CONFLICT (code) DO NOTHING; +INSERT INTO currency (code, name, symbol, decimal_digits) VALUES ('RSD', 'Serbian Dinar', 'din.', 0) ON CONFLICT (code) DO NOTHING; +INSERT INTO currency (code, name, symbol, decimal_digits) VALUES ('RUB', 'Russian Ruble', 'RUB', 2) ON CONFLICT (code) DO NOTHING; +INSERT INTO currency (code, name, symbol, decimal_digits) VALUES ('RWF', 'Rwandan Franc', 'RWF', 0) ON CONFLICT (code) DO NOTHING; +INSERT INTO currency (code, name, symbol, decimal_digits) VALUES ('SAR', 'Saudi Riyal', 'SR', 2) ON CONFLICT (code) DO NOTHING; +INSERT INTO currency (code, name, symbol, decimal_digits) VALUES ('SBD', 'Solomon Islands Dollar', 'SI$', 2) ON CONFLICT (code) DO NOTHING; +INSERT INTO currency (code, name, symbol, decimal_digits) VALUES ('SCR', 'Seychellois Rupee', 'SRe', 2) ON CONFLICT (code) DO NOTHING; +INSERT INTO currency (code, name, symbol, decimal_digits) VALUES ('SDG', 'Sudanese Pound', 'SDG', 2) ON CONFLICT (code) DO NOTHING; +INSERT INTO currency (code, name, symbol, decimal_digits) VALUES ('SEK', 'Swedish Krona', 'Skr', 2) ON CONFLICT (code) DO NOTHING; +INSERT INTO currency (code, name, symbol, decimal_digits) VALUES ('SGD', 'Singapore Dollar', 'S$', 2) ON CONFLICT (code) DO NOTHING; +INSERT INTO currency (code, name, symbol, decimal_digits) VALUES ('SHP', 'Saint Helena Pound', '£', 2) ON CONFLICT (code) DO NOTHING; +INSERT INTO currency (code, name, symbol, decimal_digits) VALUES ('SLL', 'Sierra Leonean Leone', 'Le', 2) ON CONFLICT (code) DO NOTHING; +INSERT INTO currency (code, name, symbol, decimal_digits) VALUES ('SOS', 'Somali Shilling', 'Ssh', 0) ON CONFLICT (code) DO NOTHING; +INSERT INTO currency (code, name, symbol, decimal_digits) VALUES ('SRD', 'Surinamese Dollar', '$', 2) ON CONFLICT (code) DO NOTHING; +INSERT INTO currency (code, name, symbol, decimal_digits) VALUES ('STD', 'São Tomé and Príncipe dobra', 'Db', 2) ON CONFLICT (code) DO NOTHING; +INSERT INTO currency (code, name, symbol, decimal_digits) VALUES ('SVC', 'Salvadoran Colón', '₡', 2) ON CONFLICT (code) DO NOTHING; +INSERT INTO currency (code, name, symbol, decimal_digits) VALUES ('SYP', 'Syrian Pound', 'SY£', 0) ON CONFLICT (code) DO NOTHING; +INSERT INTO currency (code, name, symbol, decimal_digits) VALUES ('SZL', 'Swazi Lilangeni', 'L', 2) ON CONFLICT (code) DO NOTHING; +INSERT INTO currency (code, name, symbol, decimal_digits) VALUES ('THB', 'Thai Baht', '฿', 2) ON CONFLICT (code) DO NOTHING; +INSERT INTO currency (code, name, symbol, decimal_digits) VALUES ('TJS', 'Tajikistani Somoni', 'TJS', 2) ON CONFLICT (code) DO NOTHING; +INSERT INTO currency (code, name, symbol, decimal_digits) VALUES ('TMT', 'Turkmenistani Manat', 'T', 2) ON CONFLICT (code) DO NOTHING; +INSERT INTO currency (code, name, symbol, decimal_digits) VALUES ('TND', 'Tunisian Dinar', 'DT',3) ON CONFLICT (code) DO NOTHING; +INSERT INTO currency (code, name, symbol, decimal_digits) VALUES ('TOP', 'Tongan Paʻanga', 'T$', 2) ON CONFLICT (code) DO NOTHING; +INSERT INTO currency (code, name, symbol, decimal_digits) VALUES ('TRY', 'Turkish Lira', 'TL', 2) ON CONFLICT (code) DO NOTHING; +INSERT INTO currency (code, name, symbol, decimal_digits) VALUES ('TTD', 'Trinidad and Tobago Dollar', 'TT$', 2) ON CONFLICT (code) DO NOTHING; +INSERT INTO currency (code, name, symbol, decimal_digits) VALUES ('TWD', 'New Taiwan Dollar', 'NT$', 2) ON CONFLICT (code) DO NOTHING; +INSERT INTO currency (code, name, symbol, decimal_digits) VALUES ('TZS', 'Tanzanian Shilling', 'TSh', 0) ON CONFLICT (code) DO NOTHING; +INSERT INTO currency (code, name, symbol, decimal_digits) VALUES ('UAH', 'Ukrainian Hryvnia', '₴', 2) ON CONFLICT (code) DO NOTHING; +INSERT INTO currency (code, name, symbol, decimal_digits) VALUES ('UGX', 'Ugandan Shilling', 'USh', 0) ON CONFLICT (code) DO NOTHING; +INSERT INTO currency (code, name, symbol, decimal_digits) VALUES ('USD', 'US Dollar', '$', 2) ON CONFLICT (code) DO NOTHING; +INSERT INTO currency (code, name, symbol, decimal_digits) VALUES ('UYU', 'Uruguayan Peso', '$U', 2) ON CONFLICT (code) DO NOTHING; +INSERT INTO currency (code, name, symbol, decimal_digits) VALUES ('UZS', 'Uzbekistan Som', 'UZS', 0) ON CONFLICT (code) DO NOTHING; +INSERT INTO currency (code, name, symbol, decimal_digits) VALUES ('VEF', 'Venezuelan Bolívar', 'Bs.F.', 2) ON CONFLICT (code) DO NOTHING; +INSERT INTO currency (code, name, symbol, decimal_digits) VALUES ('VND', 'Vietnamese Dong', '₫', 0) ON CONFLICT (code) DO NOTHING; +INSERT INTO currency (code, name, symbol, decimal_digits) VALUES ('VUV', 'Vanuatu Vatu', 'VUV', 0) ON CONFLICT (code) DO NOTHING; +INSERT INTO currency (code, name, symbol, decimal_digits) VALUES ('WST', 'Samoan Tala', 'WS$', 2) ON CONFLICT (code) DO NOTHING; +INSERT INTO currency (code, name, symbol, decimal_digits) VALUES ('XAF', 'CFA Franc BEAC', 'FCFA', 0) ON CONFLICT (code) DO NOTHING; +INSERT INTO currency (code, name, symbol, decimal_digits) VALUES ('XAG', 'Silver Ounce', 'XAG', 2) ON CONFLICT (code) DO NOTHING; +INSERT INTO currency (code, name, symbol, decimal_digits) VALUES ('XAU', 'Gold Ounce', 'XAU', 2) ON CONFLICT (code) DO NOTHING; +INSERT INTO currency (code, name, symbol, decimal_digits) VALUES ('XCD', 'East Caribbean Dollar', 'EC$', 2) ON CONFLICT (code) DO NOTHING; +INSERT INTO currency (code, name, symbol, decimal_digits) VALUES ('XDR', 'Special drawing rights', 'SDR', 2) ON CONFLICT (code) DO NOTHING; +INSERT INTO currency (code, name, symbol, decimal_digits) VALUES ('XOF', 'CFA Franc BCEAO', 'CFA', 0) ON CONFLICT (code) DO NOTHING; +INSERT INTO currency (code, name, symbol, decimal_digits) VALUES ('XPF', 'CFP Franc', 'CFP', 0) ON CONFLICT (code) DO NOTHING; +INSERT INTO currency (code, name, symbol, decimal_digits) VALUES ('YER', 'Yemeni Rial', 'YR', 0) ON CONFLICT (code) DO NOTHING; +INSERT INTO currency (code, name, symbol, decimal_digits) VALUES ('ZAR', 'South African Rand', 'R', 2) ON CONFLICT (code) DO NOTHING; +INSERT INTO currency (code, name, symbol, decimal_digits) VALUES ('ZMK', 'Zambian Kwacha', 'ZK', 0) ON CONFLICT (code) DO NOTHING; +INSERT INTO currency (code, name, symbol, decimal_digits) VALUES ('ZMW', 'Zambian Kwacha', 'ZK', 0) ON CONFLICT (code) DO NOTHING; +INSERT INTO currency (code, name, symbol, decimal_digits) VALUES ('ZWL', 'Zimbabwean dollar', 'ZWL', 2) ON CONFLICT (code) DO NOTHING; +INSERT INTO currency (code, name, symbol, decimal_digits) VALUES ('XPT', 'Platinum Ounce', 'XPT',6) ON CONFLICT (code) DO NOTHING; +INSERT INTO currency (code, name, symbol, decimal_digits) VALUES ('XPD', 'Palladium Ounce', 'XPD',6) ON CONFLICT (code) DO NOTHING; +INSERT INTO currency (code, name, symbol, decimal_digits) VALUES ('BTC', 'Bitcoin', '₿',8) ON CONFLICT (code) DO NOTHING; +INSERT INTO currency (code, name, symbol, decimal_digits) VALUES ('ETH', 'Ethereum', 'Ξ',18) ON CONFLICT (code) DO NOTHING; +INSERT INTO currency (code, name, symbol, decimal_digits) VALUES ('BNB', 'Binance', 'BNB',8) ON CONFLICT (code) DO NOTHING; +INSERT INTO currency (code, name, symbol, decimal_digits) VALUES ('XRP', 'Ripple', 'XRP',6) ON CONFLICT (code) DO NOTHING; +INSERT INTO currency (code, name, symbol, decimal_digits) VALUES ('SOL', 'Solana', 'SOL',9) ON CONFLICT (code) DO NOTHING; +INSERT INTO currency (code, name, symbol, decimal_digits) VALUES ('DOT', 'Polkadot', 'DOT',10) ON CONFLICT (code) DO NOTHING; +INSERT INTO currency (code, name, symbol, decimal_digits) VALUES ('AVAX', 'Avalanche', 'AVAX',18) ON CONFLICT (code) DO NOTHING; +INSERT INTO currency (code, name, symbol, decimal_digits) VALUES ('MATIC', 'Matic Token', 'MATIC',18) ON CONFLICT (code) DO NOTHING; +INSERT INTO currency (code, name, symbol, decimal_digits) VALUES ('LTC', 'Litecoin', 'Ł',8) ON CONFLICT (code) DO NOTHING; +INSERT INTO currency (code, name, symbol, decimal_digits) VALUES ('ADA', 'Cardano', 'ADA',6) ON CONFLICT (code) DO NOTHING; +INSERT INTO currency (code, name, symbol, decimal_digits) VALUES ('USDT', 'Tether', 'USDT', 2) ON CONFLICT (code) DO NOTHING; +INSERT INTO currency (code, name, symbol, decimal_digits) VALUES ('USDC', 'USD Coin', 'USDC', 2) ON CONFLICT (code) DO NOTHING; +INSERT INTO currency (code, name, symbol, decimal_digits) VALUES ('DAI', 'Dai', 'DAI', 2) ON CONFLICT (code) DO NOTHING; +INSERT INTO currency (code, name, symbol, decimal_digits) VALUES ('ARB', 'Arbitrum', 'ARB',8) ON CONFLICT (code) DO NOTHING; +INSERT INTO currency (code, name, symbol, decimal_digits) VALUES ('OP', 'Optimism', 'OP',8) ON CONFLICT (code) DO NOTHING; +INSERT INTO currency (code, name, symbol, decimal_digits) VALUES ('VES', 'Venezuelan Bolívar', 'Bs.S.', 2) ON CONFLICT (code) DO NOTHING; +INSERT INTO currency (code, name, symbol, decimal_digits) VALUES ('STN', 'São Tomé and Príncipe dobra', 'STN', 2) ON CONFLICT (code) DO NOTHING; +INSERT INTO currency (code, name, symbol, decimal_digits) VALUES ('MRU', 'Mauritanian ouguiya', 'MRU', 2) ON CONFLICT (code) DO NOTHING; + +-- Insert exchange rates into the rate table +-- These inserts will not execute if the rate with the same currency_id already exists +INSERT INTO rate (currency_id, rate, updated_at) SELECT c.id, 2.2679298487, '2024-06-10T23:59:59Z' FROM currency c WHERE c.code = 'ADA' ON CONFLICT (currency_id, updated_at) DO NOTHING; +INSERT INTO rate (currency_id, rate, updated_at) SELECT c.id, 3.6718406559, '2024-06-10T23:59:59Z' FROM currency c WHERE c.code = 'AED' ON CONFLICT (currency_id, updated_at) DO NOTHING; +INSERT INTO rate (currency_id, rate, updated_at) SELECT c.id, 70.7904573332, '2024-06-10T23:59:59Z' FROM currency c WHERE c.code = 'AFN' ON CONFLICT (currency_id, updated_at) DO NOTHING; +INSERT INTO rate (currency_id, rate, updated_at) SELECT c.id, 93.0320944204, '2024-06-10T23:59:59Z' FROM currency c WHERE c.code = 'ALL' ON CONFLICT (currency_id, updated_at) DO NOTHING; +INSERT INTO rate (currency_id, rate, updated_at) SELECT c.id, 387.0652417589, '2024-06-10T23:59:59Z' FROM currency c WHERE c.code = 'AMD' ON CONFLICT (currency_id, updated_at) DO NOTHING; +INSERT INTO rate (currency_id, rate, updated_at) SELECT c.id, 1.787280302, '2024-06-10T23:59:59Z' FROM currency c WHERE c.code = 'ANG' ON CONFLICT (currency_id, updated_at) DO NOTHING; +INSERT INTO rate (currency_id, rate, updated_at) SELECT c.id, 851.5743892967, '2024-06-10T23:59:59Z' FROM currency c WHERE c.code = 'AOA' ON CONFLICT (currency_id, updated_at) DO NOTHING; +INSERT INTO rate (currency_id, rate, updated_at) SELECT c.id, 1.0411768981, '2024-06-10T23:59:59Z' FROM currency c WHERE c.code = 'ARB' ON CONFLICT (currency_id, updated_at) DO NOTHING; +INSERT INTO rate (currency_id, rate, updated_at) SELECT c.id, 901.9165772212, '2024-06-10T23:59:59Z' FROM currency c WHERE c.code = 'ARS' ON CONFLICT (currency_id, updated_at) DO NOTHING; +INSERT INTO rate (currency_id, rate, updated_at) SELECT c.id, 1.5132802995, '2024-06-10T23:59:59Z' FROM currency c WHERE c.code = 'AUD' ON CONFLICT (currency_id, updated_at) DO NOTHING; +INSERT INTO rate (currency_id, rate, updated_at) SELECT c.id, 0.0309532227, '2024-06-10T23:59:59Z' FROM currency c WHERE c.code = 'AVAX' ON CONFLICT (currency_id, updated_at) DO NOTHING; +INSERT INTO rate (currency_id, rate, updated_at) SELECT c.id, 1.79, '2024-06-10T23:59:59Z' FROM currency c WHERE c.code = 'AWG' ON CONFLICT (currency_id, updated_at) DO NOTHING; +INSERT INTO rate (currency_id, rate, updated_at) SELECT c.id, 1.7, '2024-06-10T23:59:59Z' FROM currency c WHERE c.code = 'AZN' ON CONFLICT (currency_id, updated_at) DO NOTHING; +INSERT INTO rate (currency_id, rate, updated_at) SELECT c.id, 1.8167003404, '2024-06-10T23:59:59Z' FROM currency c WHERE c.code = 'BAM' ON CONFLICT (currency_id, updated_at) DO NOTHING; +INSERT INTO rate (currency_id, rate, updated_at) SELECT c.id, 2, '2024-06-10T23:59:59Z' FROM currency c WHERE c.code = 'BBD' ON CONFLICT (currency_id, updated_at) DO NOTHING; +INSERT INTO rate (currency_id, rate, updated_at) SELECT c.id, 117.5903174764, '2024-06-10T23:59:59Z' FROM currency c WHERE c.code = 'BDT' ON CONFLICT (currency_id, updated_at) DO NOTHING; +INSERT INTO rate (currency_id, rate, updated_at) SELECT c.id, 1.812830352, '2024-06-10T23:59:59Z' FROM currency c WHERE c.code = 'BGN' ON CONFLICT (currency_id, updated_at) DO NOTHING; +INSERT INTO rate (currency_id, rate, updated_at) SELECT c.id, 0.376, '2024-06-10T23:59:59Z' FROM currency c WHERE c.code = 'BHD' ON CONFLICT (currency_id, updated_at) DO NOTHING; +INSERT INTO rate (currency_id, rate, updated_at) SELECT c.id, 2874.4080899246, '2024-06-10T23:59:59Z' FROM currency c WHERE c.code = 'BIF' ON CONFLICT (currency_id, updated_at) DO NOTHING; +INSERT INTO rate (currency_id, rate, updated_at) SELECT c.id, 1, '2024-06-10T23:59:59Z' FROM currency c WHERE c.code = 'BMD' ON CONFLICT (currency_id, updated_at) DO NOTHING; +INSERT INTO rate (currency_id, rate, updated_at) SELECT c.id, 0.0015946085, '2024-06-10T23:59:59Z' FROM currency c WHERE c.code = 'BNB' ON CONFLICT (currency_id, updated_at) DO NOTHING; +INSERT INTO rate (currency_id, rate, updated_at) SELECT c.id, 1.3515501742, '2024-06-10T23:59:59Z' FROM currency c WHERE c.code = 'BND' ON CONFLICT (currency_id, updated_at) DO NOTHING; +INSERT INTO rate (currency_id, rate, updated_at) SELECT c.id, 6.9369912102, '2024-06-10T23:59:59Z' FROM currency c WHERE c.code = 'BOB' ON CONFLICT (currency_id, updated_at) DO NOTHING; +INSERT INTO rate (currency_id, rate, updated_at) SELECT c.id, 5.3570006385, '2024-06-10T23:59:59Z' FROM currency c WHERE c.code = 'BRL' ON CONFLICT (currency_id, updated_at) DO NOTHING; +INSERT INTO rate (currency_id, rate, updated_at) SELECT c.id, 1, '2024-06-10T23:59:59Z' FROM currency c WHERE c.code = 'BSD' ON CONFLICT (currency_id, updated_at) DO NOTHING; +INSERT INTO rate (currency_id, rate, updated_at) SELECT c.id, 1.43517e-5, '2024-06-10T23:59:59Z' FROM currency c WHERE c.code = 'BTC' ON CONFLICT (currency_id, updated_at) DO NOTHING; +INSERT INTO rate (currency_id, rate, updated_at) SELECT c.id, 83.5026750681, '2024-06-10T23:59:59Z' FROM currency c WHERE c.code = 'BTN' ON CONFLICT (currency_id, updated_at) DO NOTHING; +INSERT INTO rate (currency_id, rate, updated_at) SELECT c.id, 13.7488325074, '2024-06-10T23:59:59Z' FROM currency c WHERE c.code = 'BWP' ON CONFLICT (currency_id, updated_at) DO NOTHING; +INSERT INTO rate (currency_id, rate, updated_at) SELECT c.id, 3.2699770637, '2024-06-10T23:59:59Z' FROM currency c WHERE c.code = 'BYN' ON CONFLICT (currency_id, updated_at) DO NOTHING; +INSERT INTO rate (currency_id, rate, updated_at) SELECT c.id, 32699.762594218, '2024-06-10T23:59:59Z' FROM currency c WHERE c.code = 'BYR' ON CONFLICT (currency_id, updated_at) DO NOTHING; +INSERT INTO rate (currency_id, rate, updated_at) SELECT c.id, 2, '2024-06-10T23:59:59Z' FROM currency c WHERE c.code = 'BZD' ON CONFLICT (currency_id, updated_at) DO NOTHING; +INSERT INTO rate (currency_id, rate, updated_at) SELECT c.id, 1.3758702027, '2024-06-10T23:59:59Z' FROM currency c WHERE c.code = 'CAD' ON CONFLICT (currency_id, updated_at) DO NOTHING; +INSERT INTO rate (currency_id, rate, updated_at) SELECT c.id, 2813.442771238, '2024-06-10T23:59:59Z' FROM currency c WHERE c.code = 'CDF' ON CONFLICT (currency_id, updated_at) DO NOTHING; +INSERT INTO rate (currency_id, rate, updated_at) SELECT c.id, 0.8965300987, '2024-06-10T23:59:59Z' FROM currency c WHERE c.code = 'CHF' ON CONFLICT (currency_id, updated_at) DO NOTHING; +INSERT INTO rate (currency_id, rate, updated_at) SELECT c.id, 0.0242000032, '2024-06-10T23:59:59Z' FROM currency c WHERE c.code = 'CLF' ON CONFLICT (currency_id, updated_at) DO NOTHING; +INSERT INTO rate (currency_id, rate, updated_at) SELECT c.id, 921.0413972452, '2024-06-10T23:59:59Z' FROM currency c WHERE c.code = 'CLP' ON CONFLICT (currency_id, updated_at) DO NOTHING; +INSERT INTO rate (currency_id, rate, updated_at) SELECT c.id, 7.2501309285, '2024-06-10T23:59:59Z' FROM currency c WHERE c.code = 'CNY' ON CONFLICT (currency_id, updated_at) DO NOTHING; +INSERT INTO rate (currency_id, rate, updated_at) SELECT c.id, 3937.3301467823, '2024-06-10T23:59:59Z' FROM currency c WHERE c.code = 'COP' ON CONFLICT (currency_id, updated_at) DO NOTHING; +INSERT INTO rate (currency_id, rate, updated_at) SELECT c.id, 530.3078152726, '2024-06-10T23:59:59Z' FROM currency c WHERE c.code = 'CRC' ON CONFLICT (currency_id, updated_at) DO NOTHING; +INSERT INTO rate (currency_id, rate, updated_at) SELECT c.id, 1, '2024-06-10T23:59:59Z' FROM currency c WHERE c.code = 'CUC' ON CONFLICT (currency_id, updated_at) DO NOTHING; +INSERT INTO rate (currency_id, rate, updated_at) SELECT c.id, 24, '2024-06-10T23:59:59Z' FROM currency c WHERE c.code = 'CUP' ON CONFLICT (currency_id, updated_at) DO NOTHING; +INSERT INTO rate (currency_id, rate, updated_at) SELECT c.id, 102.4397237684, '2024-06-10T23:59:59Z' FROM currency c WHERE c.code = 'CVE' ON CONFLICT (currency_id, updated_at) DO NOTHING; +INSERT INTO rate (currency_id, rate, updated_at) SELECT c.id, 22.8602433711, '2024-06-10T23:59:59Z' FROM currency c WHERE c.code = 'CZK' ON CONFLICT (currency_id, updated_at) DO NOTHING; +INSERT INTO rate (currency_id, rate, updated_at) SELECT c.id, 0.9992989749, '2024-06-10T23:59:59Z' FROM currency c WHERE c.code = 'DAI' ON CONFLICT (currency_id, updated_at) DO NOTHING; +INSERT INTO rate (currency_id, rate, updated_at) SELECT c.id, 177.721, '2024-06-10T23:59:59Z' FROM currency c WHERE c.code = 'DJF' ON CONFLICT (currency_id, updated_at) DO NOTHING; +INSERT INTO rate (currency_id, rate, updated_at) SELECT c.id, 6.9279413087, '2024-06-10T23:59:59Z' FROM currency c WHERE c.code = 'DKK' ON CONFLICT (currency_id, updated_at) DO NOTHING; +INSERT INTO rate (currency_id, rate, updated_at) SELECT c.id, 59.4084267105, '2024-06-10T23:59:59Z' FROM currency c WHERE c.code = 'DOP' ON CONFLICT (currency_id, updated_at) DO NOTHING; +INSERT INTO rate (currency_id, rate, updated_at) SELECT c.id, 0.1535316848, '2024-06-10T23:59:59Z' FROM currency c WHERE c.code = 'DOT' ON CONFLICT (currency_id, updated_at) DO NOTHING; +INSERT INTO rate (currency_id, rate, updated_at) SELECT c.id, 134.9637578508, '2024-06-10T23:59:59Z' FROM currency c WHERE c.code = 'DZD' ON CONFLICT (currency_id, updated_at) DO NOTHING; +INSERT INTO rate (currency_id, rate, updated_at) SELECT c.id, 47.718617933, '2024-06-10T23:59:59Z' FROM currency c WHERE c.code = 'EGP' ON CONFLICT (currency_id, updated_at) DO NOTHING; +INSERT INTO rate (currency_id, rate, updated_at) SELECT c.id, 15, '2024-06-10T23:59:59Z' FROM currency c WHERE c.code = 'ERN' ON CONFLICT (currency_id, updated_at) DO NOTHING; +INSERT INTO rate (currency_id, rate, updated_at) SELECT c.id, 56.9702884224, '2024-06-10T23:59:59Z' FROM currency c WHERE c.code = 'ETB' ON CONFLICT (currency_id, updated_at) DO NOTHING; +INSERT INTO rate (currency_id, rate, updated_at) SELECT c.id, 0.0002724481, '2024-06-10T23:59:59Z' FROM currency c WHERE c.code = 'ETH' ON CONFLICT (currency_id, updated_at) DO NOTHING; +INSERT INTO rate (currency_id, rate, updated_at) SELECT c.id, 0.9290801473, '2024-06-10T23:59:59Z' FROM currency c WHERE c.code = 'EUR' ON CONFLICT (currency_id, updated_at) DO NOTHING; +INSERT INTO rate (currency_id, rate, updated_at) SELECT c.id, 2.2394803828, '2024-06-10T23:59:59Z' FROM currency c WHERE c.code = 'FJD' ON CONFLICT (currency_id, updated_at) DO NOTHING; +INSERT INTO rate (currency_id, rate, updated_at) SELECT c.id, 0.7857969946, '2024-06-10T23:59:59Z' FROM currency c WHERE c.code = 'FKP' ON CONFLICT (currency_id, updated_at) DO NOTHING; +INSERT INTO rate (currency_id, rate, updated_at) SELECT c.id, 0.7857400856, '2024-06-10T23:59:59Z' FROM currency c WHERE c.code = 'GBP' ON CONFLICT (currency_id, updated_at) DO NOTHING; +INSERT INTO rate (currency_id, rate, updated_at) SELECT c.id, 2.8200704628, '2024-06-10T23:59:59Z' FROM currency c WHERE c.code = 'GEL' ON CONFLICT (currency_id, updated_at) DO NOTHING; +INSERT INTO rate (currency_id, rate, updated_at) SELECT c.id, 0.7857970595, '2024-06-10T23:59:59Z' FROM currency c WHERE c.code = 'GGP' ON CONFLICT (currency_id, updated_at) DO NOTHING; +INSERT INTO rate (currency_id, rate, updated_at) SELECT c.id, 14.9096017346, '2024-06-10T23:59:59Z' FROM currency c WHERE c.code = 'GHS' ON CONFLICT (currency_id, updated_at) DO NOTHING; +INSERT INTO rate (currency_id, rate, updated_at) SELECT c.id, 0.7857973594, '2024-06-10T23:59:59Z' FROM currency c WHERE c.code = 'GIP' ON CONFLICT (currency_id, updated_at) DO NOTHING; +INSERT INTO rate (currency_id, rate, updated_at) SELECT c.id, 58.2027859377, '2024-06-10T23:59:59Z' FROM currency c WHERE c.code = 'GMD' ON CONFLICT (currency_id, updated_at) DO NOTHING; +INSERT INTO rate (currency_id, rate, updated_at) SELECT c.id, 8587.8742589067, '2024-06-10T23:59:59Z' FROM currency c WHERE c.code = 'GNF' ON CONFLICT (currency_id, updated_at) DO NOTHING; +INSERT INTO rate (currency_id, rate, updated_at) SELECT c.id, 7.758281421, '2024-06-10T23:59:59Z' FROM currency c WHERE c.code = 'GTQ' ON CONFLICT (currency_id, updated_at) DO NOTHING; +INSERT INTO rate (currency_id, rate, updated_at) SELECT c.id, 209.1970978965, '2024-06-10T23:59:59Z' FROM currency c WHERE c.code = 'GYD' ON CONFLICT (currency_id, updated_at) DO NOTHING; +INSERT INTO rate (currency_id, rate, updated_at) SELECT c.id, 7.8103013923, '2024-06-10T23:59:59Z' FROM currency c WHERE c.code = 'HKD' ON CONFLICT (currency_id, updated_at) DO NOTHING; +INSERT INTO rate (currency_id, rate, updated_at) SELECT c.id, 24.7146626422, '2024-06-10T23:59:59Z' FROM currency c WHERE c.code = 'HNL' ON CONFLICT (currency_id, updated_at) DO NOTHING; +INSERT INTO rate (currency_id, rate, updated_at) SELECT c.id, 6.6516508793, '2024-06-10T23:59:59Z' FROM currency c WHERE c.code = 'HRK' ON CONFLICT (currency_id, updated_at) DO NOTHING; +INSERT INTO rate (currency_id, rate, updated_at) SELECT c.id, 134.7838194535, '2024-06-10T23:59:59Z' FROM currency c WHERE c.code = 'HTG' ON CONFLICT (currency_id, updated_at) DO NOTHING; +INSERT INTO rate (currency_id, rate, updated_at) SELECT c.id, 364.8563849752, '2024-06-10T23:59:59Z' FROM currency c WHERE c.code = 'HUF' ON CONFLICT (currency_id, updated_at) DO NOTHING; +INSERT INTO rate (currency_id, rate, updated_at) SELECT c.id, 16267.59254452, '2024-06-10T23:59:59Z' FROM currency c WHERE c.code = 'IDR' ON CONFLICT (currency_id, updated_at) DO NOTHING; +INSERT INTO rate (currency_id, rate, updated_at) SELECT c.id, 3.7498404871, '2024-06-10T23:59:59Z' FROM currency c WHERE c.code = 'ILS' ON CONFLICT (currency_id, updated_at) DO NOTHING; +INSERT INTO rate (currency_id, rate, updated_at) SELECT c.id, 0.78579737, '2024-06-10T23:59:59Z' FROM currency c WHERE c.code = 'IMP' ON CONFLICT (currency_id, updated_at) DO NOTHING; +INSERT INTO rate (currency_id, rate, updated_at) SELECT c.id, 83.5191700278, '2024-06-10T23:59:59Z' FROM currency c WHERE c.code = 'INR' ON CONFLICT (currency_id, updated_at) DO NOTHING; +INSERT INTO rate (currency_id, rate, updated_at) SELECT c.id, 1308.6456434348, '2024-06-10T23:59:59Z' FROM currency c WHERE c.code = 'IQD' ON CONFLICT (currency_id, updated_at) DO NOTHING; +INSERT INTO rate (currency_id, rate, updated_at) SELECT c.id, 42019.979875943, '2024-06-10T23:59:59Z' FROM currency c WHERE c.code = 'IRR' ON CONFLICT (currency_id, updated_at) DO NOTHING; +INSERT INTO rate (currency_id, rate, updated_at) SELECT c.id, 139.2122370989, '2024-06-10T23:59:59Z' FROM currency c WHERE c.code = 'ISK' ON CONFLICT (currency_id, updated_at) DO NOTHING; +INSERT INTO rate (currency_id, rate, updated_at) SELECT c.id, 0.7857974327, '2024-06-10T23:59:59Z' FROM currency c WHERE c.code = 'JEP' ON CONFLICT (currency_id, updated_at) DO NOTHING; +INSERT INTO rate (currency_id, rate, updated_at) SELECT c.id, 155.2491448209, '2024-06-10T23:59:59Z' FROM currency c WHERE c.code = 'JMD' ON CONFLICT (currency_id, updated_at) DO NOTHING; +INSERT INTO rate (currency_id, rate, updated_at) SELECT c.id, 0.71, '2024-06-10T23:59:59Z' FROM currency c WHERE c.code = 'JOD' ON CONFLICT (currency_id, updated_at) DO NOTHING; +INSERT INTO rate (currency_id, rate, updated_at) SELECT c.id, 157.014696987, '2024-06-10T23:59:59Z' FROM currency c WHERE c.code = 'JPY' ON CONFLICT (currency_id, updated_at) DO NOTHING; +INSERT INTO rate (currency_id, rate, updated_at) SELECT c.id, 129.3930234152, '2024-06-10T23:59:59Z' FROM currency c WHERE c.code = 'KES' ON CONFLICT (currency_id, updated_at) DO NOTHING; +INSERT INTO rate (currency_id, rate, updated_at) SELECT c.id, 86.7532661567, '2024-06-10T23:59:59Z' FROM currency c WHERE c.code = 'KGS' ON CONFLICT (currency_id, updated_at) DO NOTHING; +INSERT INTO rate (currency_id, rate, updated_at) SELECT c.id, 4112.3295097742, '2024-06-10T23:59:59Z' FROM currency c WHERE c.code = 'KHR' ON CONFLICT (currency_id, updated_at) DO NOTHING; +INSERT INTO rate (currency_id, rate, updated_at) SELECT c.id, 458.5398427806, '2024-06-10T23:59:59Z' FROM currency c WHERE c.code = 'KMF' ON CONFLICT (currency_id, updated_at) DO NOTHING; +INSERT INTO rate (currency_id, rate, updated_at) SELECT c.id, 900.002639873, '2024-06-10T23:59:59Z' FROM currency c WHERE c.code = 'KPW' ON CONFLICT (currency_id, updated_at) DO NOTHING; +INSERT INTO rate (currency_id, rate, updated_at) SELECT c.id, 1372.7226101559, '2024-06-10T23:59:59Z' FROM currency c WHERE c.code = 'KRW' ON CONFLICT (currency_id, updated_at) DO NOTHING; +INSERT INTO rate (currency_id, rate, updated_at) SELECT c.id, 0.3068600384, '2024-06-10T23:59:59Z' FROM currency c WHERE c.code = 'KWD' ON CONFLICT (currency_id, updated_at) DO NOTHING; +INSERT INTO rate (currency_id, rate, updated_at) SELECT c.id, 0.83333, '2024-06-10T23:59:59Z' FROM currency c WHERE c.code = 'KYD' ON CONFLICT (currency_id, updated_at) DO NOTHING; +INSERT INTO rate (currency_id, rate, updated_at) SELECT c.id, 448.5436886523, '2024-06-10T23:59:59Z' FROM currency c WHERE c.code = 'KZT' ON CONFLICT (currency_id, updated_at) DO NOTHING; +INSERT INTO rate (currency_id, rate, updated_at) SELECT c.id, 21719.745960039, '2024-06-10T23:59:59Z' FROM currency c WHERE c.code = 'LAK' ON CONFLICT (currency_id, updated_at) DO NOTHING; +INSERT INTO rate (currency_id, rate, updated_at) SELECT c.id, 89589.77614652, '2024-06-10T23:59:59Z' FROM currency c WHERE c.code = 'LBP' ON CONFLICT (currency_id, updated_at) DO NOTHING; +INSERT INTO rate (currency_id, rate, updated_at) SELECT c.id, 301.8763925636, '2024-06-10T23:59:59Z' FROM currency c WHERE c.code = 'LKR' ON CONFLICT (currency_id, updated_at) DO NOTHING; +INSERT INTO rate (currency_id, rate, updated_at) SELECT c.id, 194.250797033, '2024-06-10T23:59:59Z' FROM currency c WHERE c.code = 'LRD' ON CONFLICT (currency_id, updated_at) DO NOTHING; +INSERT INTO rate (currency_id, rate, updated_at) SELECT c.id, 18.740072297, '2024-06-10T23:59:59Z' FROM currency c WHERE c.code = 'LSL' ON CONFLICT (currency_id, updated_at) DO NOTHING; +INSERT INTO rate (currency_id, rate, updated_at) SELECT c.id, 0.012540671, '2024-06-10T23:59:59Z' FROM currency c WHERE c.code = 'LTC' ON CONFLICT (currency_id, updated_at) DO NOTHING; +INSERT INTO rate (currency_id, rate, updated_at) SELECT c.id, 3.2079216576, '2024-06-10T23:59:59Z' FROM currency c WHERE c.code = 'LTL' ON CONFLICT (currency_id, updated_at) DO NOTHING; +INSERT INTO rate (currency_id, rate, updated_at) SELECT c.id, 0.6529558367, '2024-06-10T23:59:59Z' FROM currency c WHERE c.code = 'LVL' ON CONFLICT (currency_id, updated_at) DO NOTHING; +INSERT INTO rate (currency_id, rate, updated_at) SELECT c.id, 4.8691905541, '2024-06-10T23:59:59Z' FROM currency c WHERE c.code = 'LYD' ON CONFLICT (currency_id, updated_at) DO NOTHING; +INSERT INTO rate (currency_id, rate, updated_at) SELECT c.id, 9.9428813366, '2024-06-10T23:59:59Z' FROM currency c WHERE c.code = 'MAD' ON CONFLICT (currency_id, updated_at) DO NOTHING; +INSERT INTO rate (currency_id, rate, updated_at) SELECT c.id, 1.5370217613, '2024-06-10T23:59:59Z' FROM currency c WHERE c.code = 'MATIC' ON CONFLICT (currency_id, updated_at) DO NOTHING; +INSERT INTO rate (currency_id, rate, updated_at) SELECT c.id, 17.6269922085, '2024-06-10T23:59:59Z' FROM currency c WHERE c.code = 'MDL' ON CONFLICT (currency_id, updated_at) DO NOTHING; +INSERT INTO rate (currency_id, rate, updated_at) SELECT c.id, 4480.2747953954, '2024-06-10T23:59:59Z' FROM currency c WHERE c.code = 'MGA' ON CONFLICT (currency_id, updated_at) DO NOTHING; +INSERT INTO rate (currency_id, rate, updated_at) SELECT c.id, 57.3124669066, '2024-06-10T23:59:59Z' FROM currency c WHERE c.code = 'MKD' ON CONFLICT (currency_id, updated_at) DO NOTHING; +INSERT INTO rate (currency_id, rate, updated_at) SELECT c.id, 2095.624577851, '2024-06-10T23:59:59Z' FROM currency c WHERE c.code = 'MMK' ON CONFLICT (currency_id, updated_at) DO NOTHING; +INSERT INTO rate (currency_id, rate, updated_at) SELECT c.id, 3399.6088715777, '2024-06-10T23:59:59Z' FROM currency c WHERE c.code = 'MNT' ON CONFLICT (currency_id, updated_at) DO NOTHING; +INSERT INTO rate (currency_id, rate, updated_at) SELECT c.id, 8.0643411333, '2024-06-10T23:59:59Z' FROM currency c WHERE c.code = 'MOP' ON CONFLICT (currency_id, updated_at) DO NOTHING; +INSERT INTO rate (currency_id, rate, updated_at) SELECT c.id, 356.999828, '2024-06-10T23:59:59Z' FROM currency c WHERE c.code = 'MRO' ON CONFLICT (currency_id, updated_at) DO NOTHING; +INSERT INTO rate (currency_id, rate, updated_at) SELECT c.id, 39.5001473678, '2024-06-10T23:59:59Z' FROM currency c WHERE c.code = 'MRU' ON CONFLICT (currency_id, updated_at) DO NOTHING; +INSERT INTO rate (currency_id, rate, updated_at) SELECT c.id, 46.5937585965, '2024-06-10T23:59:59Z' FROM currency c WHERE c.code = 'MUR' ON CONFLICT (currency_id, updated_at) DO NOTHING; +INSERT INTO rate (currency_id, rate, updated_at) SELECT c.id, 15.4579725536, '2024-06-10T23:59:59Z' FROM currency c WHERE c.code = 'MVR' ON CONFLICT (currency_id, updated_at) DO NOTHING; +INSERT INTO rate (currency_id, rate, updated_at) SELECT c.id, 1734.397497359, '2024-06-10T23:59:59Z' FROM currency c WHERE c.code = 'MWK' ON CONFLICT (currency_id, updated_at) DO NOTHING; +INSERT INTO rate (currency_id, rate, updated_at) SELECT c.id, 18.55754282, '2024-06-10T23:59:59Z' FROM currency c WHERE c.code = 'MXN' ON CONFLICT (currency_id, updated_at) DO NOTHING; +INSERT INTO rate (currency_id, rate, updated_at) SELECT c.id, 4.7216406039, '2024-06-10T23:59:59Z' FROM currency c WHERE c.code = 'MYR' ON CONFLICT (currency_id, updated_at) DO NOTHING; +INSERT INTO rate (currency_id, rate, updated_at) SELECT c.id, 63.6050485402, '2024-06-10T23:59:59Z' FROM currency c WHERE c.code = 'MZN' ON CONFLICT (currency_id, updated_at) DO NOTHING; +INSERT INTO rate (currency_id, rate, updated_at) SELECT c.id, 18.6629134402, '2024-06-10T23:59:59Z' FROM currency c WHERE c.code = 'NAD' ON CONFLICT (currency_id, updated_at) DO NOTHING; +INSERT INTO rate (currency_id, rate, updated_at) SELECT c.id, 1465.9738916374, '2024-06-10T23:59:59Z' FROM currency c WHERE c.code = 'NGN' ON CONFLICT (currency_id, updated_at) DO NOTHING; +INSERT INTO rate (currency_id, rate, updated_at) SELECT c.id, 36.7972487975, '2024-06-10T23:59:59Z' FROM currency c WHERE c.code = 'NIO' ON CONFLICT (currency_id, updated_at) DO NOTHING; +INSERT INTO rate (currency_id, rate, updated_at) SELECT c.id, 10.6432812966, '2024-06-10T23:59:59Z' FROM currency c WHERE c.code = 'NOK' ON CONFLICT (currency_id, updated_at) DO NOTHING; +INSERT INTO rate (currency_id, rate, updated_at) SELECT c.id, 133.0858524888, '2024-06-10T23:59:59Z' FROM currency c WHERE c.code = 'NPR' ON CONFLICT (currency_id, updated_at) DO NOTHING; +INSERT INTO rate (currency_id, rate, updated_at) SELECT c.id, 1.6312602969, '2024-06-10T23:59:59Z' FROM currency c WHERE c.code = 'NZD' ON CONFLICT (currency_id, updated_at) DO NOTHING; +INSERT INTO rate (currency_id, rate, updated_at) SELECT c.id, 0.3842500724, '2024-06-10T23:59:59Z' FROM currency c WHERE c.code = 'OMR' ON CONFLICT (currency_id, updated_at) DO NOTHING; +INSERT INTO rate (currency_id, rate, updated_at) SELECT c.id, 0.4569196376, '2024-06-10T23:59:59Z' FROM currency c WHERE c.code = 'OP' ON CONFLICT (currency_id, updated_at) DO NOTHING; +INSERT INTO rate (currency_id, rate, updated_at) SELECT c.id, 0.9988701052, '2024-06-10T23:59:59Z' FROM currency c WHERE c.code = 'PAB' ON CONFLICT (currency_id, updated_at) DO NOTHING; +INSERT INTO rate (currency_id, rate, updated_at) SELECT c.id, 3.7826704942, '2024-06-10T23:59:59Z' FROM currency c WHERE c.code = 'PEN' ON CONFLICT (currency_id, updated_at) DO NOTHING; +INSERT INTO rate (currency_id, rate, updated_at) SELECT c.id, 3.8124903888, '2024-06-10T23:59:59Z' FROM currency c WHERE c.code = 'PGK' ON CONFLICT (currency_id, updated_at) DO NOTHING; +INSERT INTO rate (currency_id, rate, updated_at) SELECT c.id, 58.7150182203, '2024-06-10T23:59:59Z' FROM currency c WHERE c.code = 'PHP' ON CONFLICT (currency_id, updated_at) DO NOTHING; +INSERT INTO rate (currency_id, rate, updated_at) SELECT c.id, 278.478689251, '2024-06-10T23:59:59Z' FROM currency c WHERE c.code = 'PKR' ON CONFLICT (currency_id, updated_at) DO NOTHING; +INSERT INTO rate (currency_id, rate, updated_at) SELECT c.id, 4.0171006006, '2024-06-10T23:59:59Z' FROM currency c WHERE c.code = 'PLN' ON CONFLICT (currency_id, updated_at) DO NOTHING; +INSERT INTO rate (currency_id, rate, updated_at) SELECT c.id, 7551.7849655727, '2024-06-10T23:59:59Z' FROM currency c WHERE c.code = 'PYG' ON CONFLICT (currency_id, updated_at) DO NOTHING; +INSERT INTO rate (currency_id, rate, updated_at) SELECT c.id, 3.6411907088, '2024-06-10T23:59:59Z' FROM currency c WHERE c.code = 'QAR' ON CONFLICT (currency_id, updated_at) DO NOTHING; +INSERT INTO rate (currency_id, rate, updated_at) SELECT c.id, 4.6227706077, '2024-06-10T23:59:59Z' FROM currency c WHERE c.code = 'RON' ON CONFLICT (currency_id, updated_at) DO NOTHING; +INSERT INTO rate (currency_id, rate, updated_at) SELECT c.id, 108.3531882033, '2024-06-10T23:59:59Z' FROM currency c WHERE c.code = 'RSD' ON CONFLICT (currency_id, updated_at) DO NOTHING; +INSERT INTO rate (currency_id, rate, updated_at) SELECT c.id, 88.8936112341, '2024-06-10T23:59:59Z' FROM currency c WHERE c.code = 'RUB' ON CONFLICT (currency_id, updated_at) DO NOTHING; +INSERT INTO rate (currency_id, rate, updated_at) SELECT c.id, 1301.7783053399, '2024-06-10T23:59:59Z' FROM currency c WHERE c.code = 'RWF' ON CONFLICT (currency_id, updated_at) DO NOTHING; +INSERT INTO rate (currency_id, rate, updated_at) SELECT c.id, 3.7456707423, '2024-06-10T23:59:59Z' FROM currency c WHERE c.code = 'SAR' ON CONFLICT (currency_id, updated_at) DO NOTHING; +INSERT INTO rate (currency_id, rate, updated_at) SELECT c.id, 8.3580865701, '2024-06-10T23:59:59Z' FROM currency c WHERE c.code = 'SBD' ON CONFLICT (currency_id, updated_at) DO NOTHING; +INSERT INTO rate (currency_id, rate, updated_at) SELECT c.id, 14.8189315961, '2024-06-10T23:59:59Z' FROM currency c WHERE c.code = 'SCR' ON CONFLICT (currency_id, updated_at) DO NOTHING; +INSERT INTO rate (currency_id, rate, updated_at) SELECT c.id, 601.5, '2024-06-10T23:59:59Z' FROM currency c WHERE c.code = 'SDG' ON CONFLICT (currency_id, updated_at) DO NOTHING; +INSERT INTO rate (currency_id, rate, updated_at) SELECT c.id, 10.476081969, '2024-06-10T23:59:59Z' FROM currency c WHERE c.code = 'SEK' ON CONFLICT (currency_id, updated_at) DO NOTHING; +INSERT INTO rate (currency_id, rate, updated_at) SELECT c.id, 1.3524701839, '2024-06-10T23:59:59Z' FROM currency c WHERE c.code = 'SGD' ON CONFLICT (currency_id, updated_at) DO NOTHING; +INSERT INTO rate (currency_id, rate, updated_at) SELECT c.id, 0.7857401163, '2024-06-10T23:59:59Z' FROM currency c WHERE c.code = 'SHP' ON CONFLICT (currency_id, updated_at) DO NOTHING; +INSERT INTO rate (currency_id, rate, updated_at) SELECT c.id, 22424.082202439, '2024-06-10T23:59:59Z' FROM currency c WHERE c.code = 'SLL' ON CONFLICT (currency_id, updated_at) DO NOTHING; +INSERT INTO rate (currency_id, rate, updated_at) SELECT c.id, 0.0062829578, '2024-06-10T23:59:59Z' FROM currency c WHERE c.code = 'SOL' ON CONFLICT (currency_id, updated_at) DO NOTHING; +INSERT INTO rate (currency_id, rate, updated_at) SELECT c.id, 571.0236810379, '2024-06-10T23:59:59Z' FROM currency c WHERE c.code = 'SOS' ON CONFLICT (currency_id, updated_at) DO NOTHING; +INSERT INTO rate (currency_id, rate, updated_at) SELECT c.id, 31.4496958362, '2024-06-10T23:59:59Z' FROM currency c WHERE c.code = 'SRD' ON CONFLICT (currency_id, updated_at) DO NOTHING; +INSERT INTO rate (currency_id, rate, updated_at) SELECT c.id, 22799.367791132, '2024-06-10T23:59:59Z' FROM currency c WHERE c.code = 'STD' ON CONFLICT (currency_id, updated_at) DO NOTHING; +INSERT INTO rate (currency_id, rate, updated_at) SELECT c.id, 22.7993551837, '2024-06-10T23:59:59Z' FROM currency c WHERE c.code = 'STN' ON CONFLICT (currency_id, updated_at) DO NOTHING; +INSERT INTO rate (currency_id, rate, updated_at) SELECT c.id, 8.75, '2024-06-10T23:59:59Z' FROM currency c WHERE c.code = 'SVC' ON CONFLICT (currency_id, updated_at) DO NOTHING; +INSERT INTO rate (currency_id, rate, updated_at) SELECT c.id, 12994.621714013, '2024-06-10T23:59:59Z' FROM currency c WHERE c.code = 'SYP' ON CONFLICT (currency_id, updated_at) DO NOTHING; +INSERT INTO rate (currency_id, rate, updated_at) SELECT c.id, 18.6989824867, '2024-06-10T23:59:59Z' FROM currency c WHERE c.code = 'SZL' ON CONFLICT (currency_id, updated_at) DO NOTHING; +INSERT INTO rate (currency_id, rate, updated_at) SELECT c.id, 36.7206552011, '2024-06-10T23:59:59Z' FROM currency c WHERE c.code = 'THB' ON CONFLICT (currency_id, updated_at) DO NOTHING; +INSERT INTO rate (currency_id, rate, updated_at) SELECT c.id, 10.7309711957, '2024-06-10T23:59:59Z' FROM currency c WHERE c.code = 'TJS' ON CONFLICT (currency_id, updated_at) DO NOTHING; +INSERT INTO rate (currency_id, rate, updated_at) SELECT c.id, 3.5, '2024-06-10T23:59:59Z' FROM currency c WHERE c.code = 'TMT' ON CONFLICT (currency_id, updated_at) DO NOTHING; +INSERT INTO rate (currency_id, rate, updated_at) SELECT c.id, 3.1210104046, '2024-06-10T23:59:59Z' FROM currency c WHERE c.code = 'TND' ON CONFLICT (currency_id, updated_at) DO NOTHING; +INSERT INTO rate (currency_id, rate, updated_at) SELECT c.id, 2.341250381, '2024-06-10T23:59:59Z' FROM currency c WHERE c.code = 'TOP' ON CONFLICT (currency_id, updated_at) DO NOTHING; +INSERT INTO rate (currency_id, rate, updated_at) SELECT c.id, 32.3123541442, '2024-06-10T23:59:59Z' FROM currency c WHERE c.code = 'TRY' ON CONFLICT (currency_id, updated_at) DO NOTHING; +INSERT INTO rate (currency_id, rate, updated_at) SELECT c.id, 6.7413709853, '2024-06-10T23:59:59Z' FROM currency c WHERE c.code = 'TTD' ON CONFLICT (currency_id, updated_at) DO NOTHING; +INSERT INTO rate (currency_id, rate, updated_at) SELECT c.id, 32.4375140234, '2024-06-10T23:59:59Z' FROM currency c WHERE c.code = 'TWD' ON CONFLICT (currency_id, updated_at) DO NOTHING; +INSERT INTO rate (currency_id, rate, updated_at) SELECT c.id, 2593.4598939473, '2024-06-10T23:59:59Z' FROM currency c WHERE c.code = 'TZS' ON CONFLICT (currency_id, updated_at) DO NOTHING; +INSERT INTO rate (currency_id, rate, updated_at) SELECT c.id, 40.3861148848, '2024-06-10T23:59:59Z' FROM currency c WHERE c.code = 'UAH' ON CONFLICT (currency_id, updated_at) DO NOTHING; +INSERT INTO rate (currency_id, rate, updated_at) SELECT c.id, 3793.8622299526, '2024-06-10T23:59:59Z' FROM currency c WHERE c.code = 'UGX' ON CONFLICT (currency_id, updated_at) DO NOTHING; +INSERT INTO rate (currency_id, rate, updated_at) SELECT c.id, 1, '2024-06-10T23:59:59Z' FROM currency c WHERE c.code = 'USD' ON CONFLICT (currency_id, updated_at) DO NOTHING; +INSERT INTO rate (currency_id, rate, updated_at) SELECT c.id, 0.9995108127, '2024-06-10T23:59:59Z' FROM currency c WHERE c.code = 'USDC' ON CONFLICT (currency_id, updated_at) DO NOTHING; +INSERT INTO rate (currency_id, rate, updated_at) SELECT c.id, 0.9993678097, '2024-06-10T23:59:59Z' FROM currency c WHERE c.code = 'USDT' ON CONFLICT (currency_id, updated_at) DO NOTHING; +INSERT INTO rate (currency_id, rate, updated_at) SELECT c.id, 38.8978562435, '2024-06-10T23:59:59Z' FROM currency c WHERE c.code = 'UYU' ON CONFLICT (currency_id, updated_at) DO NOTHING; +INSERT INTO rate (currency_id, rate, updated_at) SELECT c.id, 12668.129512123, '2024-06-10T23:59:59Z' FROM currency c WHERE c.code = 'UZS' ON CONFLICT (currency_id, updated_at) DO NOTHING; +INSERT INTO rate (currency_id, rate, updated_at) SELECT c.id, 3640407.7001116, '2024-06-10T23:59:59Z' FROM currency c WHERE c.code = 'VEF' ON CONFLICT (currency_id, updated_at) DO NOTHING; +INSERT INTO rate (currency_id, rate, updated_at) SELECT c.id, 36.4040600039, '2024-06-10T23:59:59Z' FROM currency c WHERE c.code = 'VES' ON CONFLICT (currency_id, updated_at) DO NOTHING; +INSERT INTO rate (currency_id, rate, updated_at) SELECT c.id, 25421.433686036, '2024-06-10T23:59:59Z' FROM currency c WHERE c.code = 'VND' ON CONFLICT (currency_id, updated_at) DO NOTHING; +INSERT INTO rate (currency_id, rate, updated_at) SELECT c.id, 119.9011334188, '2024-06-10T23:59:59Z' FROM currency c WHERE c.code = 'VUV' ON CONFLICT (currency_id, updated_at) DO NOTHING; +INSERT INTO rate (currency_id, rate, updated_at) SELECT c.id, 2.7389439335, '2024-06-10T23:59:59Z' FROM currency c WHERE c.code = 'WST' ON CONFLICT (currency_id, updated_at) DO NOTHING; +INSERT INTO rate (currency_id, rate, updated_at) SELECT c.id, 609.3574609173, '2024-06-10T23:59:59Z' FROM currency c WHERE c.code = 'XAF' ON CONFLICT (currency_id, updated_at) DO NOTHING; +INSERT INTO rate (currency_id, rate, updated_at) SELECT c.id, 0.033659259, '2024-06-10T23:59:59Z' FROM currency c WHERE c.code = 'XAG' ON CONFLICT (currency_id, updated_at) DO NOTHING; +INSERT INTO rate (currency_id, rate, updated_at) SELECT c.id, 0.0004329255, '2024-06-10T23:59:59Z' FROM currency c WHERE c.code = 'XAU' ON CONFLICT (currency_id, updated_at) DO NOTHING; +INSERT INTO rate (currency_id, rate, updated_at) SELECT c.id, 2.7, '2024-06-10T23:59:59Z' FROM currency c WHERE c.code = 'XCD' ON CONFLICT (currency_id, updated_at) DO NOTHING; +INSERT INTO rate (currency_id, rate, updated_at) SELECT c.id, 0.7558400986, '2024-06-10T23:59:59Z' FROM currency c WHERE c.code = 'XDR' ON CONFLICT (currency_id, updated_at) DO NOTHING; +INSERT INTO rate (currency_id, rate, updated_at) SELECT c.id, 609.3574713759, '2024-06-10T23:59:59Z' FROM currency c WHERE c.code = 'XOF' ON CONFLICT (currency_id, updated_at) DO NOTHING; +INSERT INTO rate (currency_id, rate, updated_at) SELECT c.id, 0.0011018596, '2024-06-10T23:59:59Z' FROM currency c WHERE c.code = 'XPD' ON CONFLICT (currency_id, updated_at) DO NOTHING; +INSERT INTO rate (currency_id, rate, updated_at) SELECT c.id, 110.7528319197, '2024-06-10T23:59:59Z' FROM currency c WHERE c.code = 'XPF' ON CONFLICT (currency_id, updated_at) DO NOTHING; +INSERT INTO rate (currency_id, rate, updated_at) SELECT c.id, 0.001027921, '2024-06-10T23:59:59Z' FROM currency c WHERE c.code = 'XPT' ON CONFLICT (currency_id, updated_at) DO NOTHING; +INSERT INTO rate (currency_id, rate, updated_at) SELECT c.id, 2.0094059521, '2024-06-10T23:59:59Z' FROM currency c WHERE c.code = 'XRP' ON CONFLICT (currency_id, updated_at) DO NOTHING; +INSERT INTO rate (currency_id, rate, updated_at) SELECT c.id, 249.9532931212, '2024-06-10T23:59:59Z' FROM currency c WHERE c.code = 'YER' ON CONFLICT (currency_id, updated_at) DO NOTHING; +INSERT INTO rate (currency_id, rate, updated_at) SELECT c.id, 18.7007830651, '2024-06-10T23:59:59Z' FROM currency c WHERE c.code = 'ZAR' ON CONFLICT (currency_id, updated_at) DO NOTHING; +INSERT INTO rate (currency_id, rate, updated_at) SELECT c.id, 9001.2, '2024-06-10T23:59:59Z' FROM currency c WHERE c.code = 'ZMK' ON CONFLICT (currency_id, updated_at) DO NOTHING; +INSERT INTO rate (currency_id, rate, updated_at) SELECT c.id, 26.4433644143, '2024-06-10T23:59:59Z' FROM currency c WHERE c.code = 'ZMW' ON CONFLICT (currency_id, updated_at) DO NOTHING; +INSERT INTO rate (currency_id, rate, updated_at) SELECT c.id, 13.541264135, '2024-06-10T23:59:59Z' FROM currency c WHERE c.code = 'ZWL' ON CONFLICT (currency_id, updated_at) DO NOTHING; \ No newline at end of file diff --git a/package.json b/package.json index 256d5587f..ea8954c0c 100644 --- a/package.json +++ b/package.json @@ -9,6 +9,7 @@ "dev": "ts-node -r tsconfig-paths/register src/index.ts", "dev:express": "ts-node-dev -r tsconfig-paths/register src/main/main-express.ts", "dev:hapi": "ts-node-dev -r tsconfig-paths/register src/main/main-hapi.ts", + "dev:express-pg": "ts-node-dev -r tsconfig-paths/register src/main/main-express-pg.ts", "test": "jest", "test:watch": "jest --watch --detectOpenHandles" }, @@ -17,7 +18,8 @@ "axios": "^1.7.2", "crypto": "^1.0.1", "currency.js": "^2.0.4", - "express": "^4.19.2" + "express": "^4.19.2", + "pg-promise": "^11.8.0" }, "devDependencies": { "@types/express": "^4.17.21", diff --git a/src/application/repository/currency-rate-repository.ts b/src/application/repository/currency-rate-repository.ts index 604c76c8f..150757a31 100644 --- a/src/application/repository/currency-rate-repository.ts +++ b/src/application/repository/currency-rate-repository.ts @@ -3,5 +3,5 @@ import CurrencyRate from "@/domain/currency-rate"; export default interface CurrencyRateRepository { findByCode(code: string): Promise; add(currencyRate: CurrencyRate): Promise; - delete(id: string): Promise; + delete(code: string): Promise; } diff --git a/src/infra/database/database-connection.ts b/src/infra/database/database-connection.ts new file mode 100644 index 000000000..503d1ab2c --- /dev/null +++ b/src/infra/database/database-connection.ts @@ -0,0 +1,4 @@ +export interface DatabaseConnection { + query(statement: string, params: TParams): Promise + close(): Promise +} \ No newline at end of file diff --git a/src/infra/database/pgpromise-adapter.ts b/src/infra/database/pgpromise-adapter.ts new file mode 100644 index 000000000..9243d2d88 --- /dev/null +++ b/src/infra/database/pgpromise-adapter.ts @@ -0,0 +1,20 @@ +import { DatabaseConnection } from "./database-connection"; + +import pgp from "pg-promise"; + +export class PgPromiseAdapter implements DatabaseConnection { + connection: any; + + constructor() { + this.connection = pgp()("postgres://postgres:123456@localhost:5432"); + } + + query(statement: string, params: any): Promise { + return this.connection.query(statement, params) + } + + close(): Promise { + return this.connection.$pool.end(); + } + +} \ No newline at end of file diff --git a/src/infra/repository/currency-rate-repository-database.ts b/src/infra/repository/currency-rate-repository-database.ts new file mode 100644 index 000000000..d06b206c9 --- /dev/null +++ b/src/infra/repository/currency-rate-repository-database.ts @@ -0,0 +1,60 @@ +import CurrencyRateRepository from "@/application/repository/currency-rate-repository"; +import CurrencyRate from "@/domain/currency-rate"; +import CurrencyRateNotFoundError from "@/domain/errors/currency-rate-not-found.error"; +import { DatabaseConnection } from "@/infra/database/database-connection"; + +export class CurrencyRateRepositoryDatabase implements CurrencyRateRepository { + + constructor(private readonly _connection: DatabaseConnection) { + } + + async findByCode(code: string): Promise { + const [row] = await this._connection.query(` + SELECT c.id, c.name, c.code, c.symbol, c.decimal_digits, r.rate + FROM currency c + JOIN rate r ON c.id = r.currency_id + WHERE c.code = $1 + ORDER BY r.updated_at DESC + LIMIT 1 + `, [code]); + if (!row) { + throw new CurrencyRateNotFoundError(code); + } + const currencyRate = new CurrencyRate( + row.id, + row.name, + row.code, + row.symbol, + row.decimal_digits, + Number(row.rate) + ); + return currencyRate; + } + + async add(currencyRate: CurrencyRate): Promise { + await this._connection.query(` + INSERT INTO currency (id, name, code, symbol, decimal_digits) + VALUES ($1, $2, $3, $4, $5)`, [ + currencyRate.id, + currencyRate.name, + currencyRate.code, + currencyRate.symbol, + currencyRate.decimalPoints, + ]); + + await this._connection.query(` + INSERT INTO rate (currency_id, rate) + VALUES ($1, $2)`, [ + currencyRate.id, + currencyRate.rate, + ]); + } + + async delete(code: string): Promise { + await this._connection.query(` + DELETE FROM currency + WHERE code = $1 + `, [code]); + } + +} \ No newline at end of file diff --git a/src/main/main-express-pg.ts b/src/main/main-express-pg.ts new file mode 100644 index 000000000..70b87b7ae --- /dev/null +++ b/src/main/main-express-pg.ts @@ -0,0 +1,18 @@ +import ConvertCurrency from "@/application/use-case/convert-currency"; +import CreateCurrencyRate from "@/application/use-case/create-currency-rate"; +import { DeleteCurrencyRateByCode } from "@/application/use-case/delete-currency-rate-by-code"; +import GetCurrencyRateByCode from "@/application/use-case/get-currency-rate-by-code"; +import { PgPromiseAdapter } from "@/infra/database/pgpromise-adapter"; +import { CurrencyController } from "@/infra/http-server/currency.controller"; +import { ExpressAdapter } from "@/infra/http-server/express-adapter"; +import { CurrencyRateRepositoryDatabase } from "@/infra/repository/currency-rate-repository-database"; + +const httpServer = new ExpressAdapter(); +const connection = new PgPromiseAdapter(); +const currencyRateRepository = new CurrencyRateRepositoryDatabase(connection); +const currencyConverter = new ConvertCurrency(currencyRateRepository) +const createCurrencyRate = new CreateCurrencyRate(currencyRateRepository) +const getCurrencyRateByCode = new GetCurrencyRateByCode(currencyRateRepository) +const deleteCurrencyRateByCode = new DeleteCurrencyRateByCode(currencyRateRepository) +new CurrencyController(httpServer, currencyConverter, createCurrencyRate, getCurrencyRateByCode, deleteCurrencyRateByCode) +httpServer.listen(3000); \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index cdff03cbf..aed921153 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1088,6 +1088,11 @@ array-flatten@1.1.1: resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2" integrity sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg== +assert-options@0.8.1: + version "0.8.1" + resolved "https://registry.yarnpkg.com/assert-options/-/assert-options-0.8.1.tgz#f1df7cef7d0b8b29a3c091e6946287a4a9a45ab8" + integrity sha512-5lNGRB5g5i2bGIzb+J1QQE1iKU/WEMVBReFIc5pPDWjcPj23otPL0eI6PB2v7QPi0qU6Mhym5D3y0ZiSIOf3GA== + asynckit@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" @@ -2641,6 +2646,77 @@ path-to-regexp@0.1.7: resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" integrity sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ== +pg-cloudflare@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/pg-cloudflare/-/pg-cloudflare-1.1.1.tgz#e6d5833015b170e23ae819e8c5d7eaedb472ca98" + integrity sha512-xWPagP/4B6BgFO+EKz3JONXv3YDgvkbVrGw2mTo3D6tVDQRh1e7cqVGvyR3BE+eQgAvx1XhW/iEASj4/jCWl3Q== + +pg-connection-string@^2.6.4: + version "2.6.4" + resolved "https://registry.yarnpkg.com/pg-connection-string/-/pg-connection-string-2.6.4.tgz#f543862adfa49fa4e14bc8a8892d2a84d754246d" + integrity sha512-v+Z7W/0EO707aNMaAEfiGnGL9sxxumwLl2fJvCQtMn9Fxsg+lPpPkdcyBSv/KFgpGdYkMfn+EI1Or2EHjpgLCA== + +pg-int8@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/pg-int8/-/pg-int8-1.0.1.tgz#943bd463bf5b71b4170115f80f8efc9a0c0eb78c" + integrity sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw== + +pg-minify@1.6.4: + version "1.6.4" + resolved "https://registry.yarnpkg.com/pg-minify/-/pg-minify-1.6.4.tgz#b8c353c6ac88985f701cf4191df6269e4956a4f7" + integrity sha512-cf6hBt1YqzqPX0OznXKSv4U7e4o7eUU4zp2zQsbJ+4OCNNr7EnnAVWkIz4k0dv6UN4ouS1ZL4WlXxCrZHHl69g== + +pg-pool@^3.6.2: + version "3.6.2" + resolved "https://registry.yarnpkg.com/pg-pool/-/pg-pool-3.6.2.tgz#3a592370b8ae3f02a7c8130d245bc02fa2c5f3f2" + integrity sha512-Htjbg8BlwXqSBQ9V8Vjtc+vzf/6fVUuak/3/XXKA9oxZprwW3IMDQTGHP+KDmVL7rtd+R1QjbnCFPuTHm3G4hg== + +pg-promise@^11.8.0: + version "11.8.0" + resolved "https://registry.yarnpkg.com/pg-promise/-/pg-promise-11.8.0.tgz#f8bad67b670163678e0387f5fb45ee1e19985c05" + integrity sha512-w9hTFpkM4FByJTJ7KCWLtZSOtQa2BKC+XIV8+3ZvDlfYfBYdz8V4V+BttnqhUPY/d12Itug7Bft4XdILihsY+w== + dependencies: + assert-options "0.8.1" + pg "8.11.5" + pg-minify "1.6.4" + spex "3.3.0" + +pg-protocol@^1.6.1: + version "1.6.1" + resolved "https://registry.yarnpkg.com/pg-protocol/-/pg-protocol-1.6.1.tgz#21333e6d83b01faaebfe7a33a7ad6bfd9ed38cb3" + integrity sha512-jPIlvgoD63hrEuihvIg+tJhoGjUsLPn6poJY9N5CnlPd91c2T18T/9zBtLxZSb1EhYxBRoZJtzScCaWlYLtktg== + +pg-types@^2.1.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/pg-types/-/pg-types-2.2.0.tgz#2d0250d636454f7cfa3b6ae0382fdfa8063254a3" + integrity sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA== + dependencies: + pg-int8 "1.0.1" + postgres-array "~2.0.0" + postgres-bytea "~1.0.0" + postgres-date "~1.0.4" + postgres-interval "^1.1.0" + +pg@8.11.5: + version "8.11.5" + resolved "https://registry.yarnpkg.com/pg/-/pg-8.11.5.tgz#e722b0a5f1ed92931c31758ebec3ddf878dd4128" + integrity sha512-jqgNHSKL5cbDjFlHyYsCXmQDrfIX/3RsNwYqpd4N0Kt8niLuNoRNH+aazv6cOd43gPh9Y4DjQCtb+X0MH0Hvnw== + dependencies: + pg-connection-string "^2.6.4" + pg-pool "^3.6.2" + pg-protocol "^1.6.1" + pg-types "^2.1.0" + pgpass "1.x" + optionalDependencies: + pg-cloudflare "^1.1.1" + +pgpass@1.x: + version "1.0.5" + resolved "https://registry.yarnpkg.com/pgpass/-/pgpass-1.0.5.tgz#9b873e4a564bb10fa7a7dbd55312728d422a223d" + integrity sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug== + dependencies: + split2 "^4.1.0" + picocolors@^1.0.0, picocolors@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.1.tgz#a8ad579b571952f0e5d25892de5445bcfe25aaa1" @@ -2663,6 +2739,28 @@ pkg-dir@^4.2.0: dependencies: find-up "^4.0.0" +postgres-array@~2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/postgres-array/-/postgres-array-2.0.0.tgz#48f8fce054fbc69671999329b8834b772652d82e" + integrity sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA== + +postgres-bytea@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/postgres-bytea/-/postgres-bytea-1.0.0.tgz#027b533c0aa890e26d172d47cf9ccecc521acd35" + integrity sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w== + +postgres-date@~1.0.4: + version "1.0.7" + resolved "https://registry.yarnpkg.com/postgres-date/-/postgres-date-1.0.7.tgz#51bc086006005e5061c591cee727f2531bf641a8" + integrity sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q== + +postgres-interval@^1.1.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/postgres-interval/-/postgres-interval-1.2.0.tgz#b460c82cb1587507788819a06aa0fffdb3544695" + integrity sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ== + dependencies: + xtend "^4.0.0" + pretty-format@^29.0.0, pretty-format@^29.7.0: version "29.7.0" resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-29.7.0.tgz#ca42c758310f365bfa71a0bda0a807160b776812" @@ -2894,6 +2992,16 @@ source-map@^0.6.0, source-map@^0.6.1: resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== +spex@3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/spex/-/spex-3.3.0.tgz#169ecc6146f2eb070d5e846d32046ea355096920" + integrity sha512-VNiXjFp6R4ldPbVRYbpxlD35yRHceecVXlct1J4/X80KuuPnW2AXMq3sGwhnJOhKkUsOxAT6nRGfGE5pocVw5w== + +split2@^4.1.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/split2/-/split2-4.2.0.tgz#c9c5920904d148bab0b9f67145f245a86aadbfa4" + integrity sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg== + sprintf-js@~1.0.2: version "1.0.3" resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" From 2a20c1b173b5f133b925324c834651351ed576f8 Mon Sep 17 00:00:00 2001 From: Leandro Reis Date: Wed, 12 Jun 2024 12:23:54 -0300 Subject: [PATCH 22/43] fix: update use-case to delete by code --- src/application/use-case/delete-currency-rate-by-code.ts | 2 +- src/infra/repository/currency-rate-repository-fake.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/application/use-case/delete-currency-rate-by-code.ts b/src/application/use-case/delete-currency-rate-by-code.ts index ab38d8d7d..1be03eade 100644 --- a/src/application/use-case/delete-currency-rate-by-code.ts +++ b/src/application/use-case/delete-currency-rate-by-code.ts @@ -11,6 +11,6 @@ export class DeleteCurrencyRateByCode { if (!currencyRate) { throw new Error('Currency rate not found'); } - await this.currencyRateRepository.delete(currencyRate.id); + await this.currencyRateRepository.delete(currencyRate.code); } } \ No newline at end of file diff --git a/src/infra/repository/currency-rate-repository-fake.ts b/src/infra/repository/currency-rate-repository-fake.ts index 05d67e139..8b9602ad5 100644 --- a/src/infra/repository/currency-rate-repository-fake.ts +++ b/src/infra/repository/currency-rate-repository-fake.ts @@ -29,8 +29,8 @@ export default class CurrencyRateRepositoryFake implements CurrencyRateRepositor this.currencyRates.push(currencyRate); } - async delete(id: string) { - const index = this.currencyRates.findIndex(currencyRate => currencyRate.id === id); + async delete(code: string) { + const index = this.currencyRates.findIndex(currencyRate => currencyRate.code === code); if (index !== -1) { this.currencyRates.splice(index, 1); } From e5c0d5be2830d08efbf9611375976ed1cacbb56f Mon Sep 17 00:00:00 2001 From: Leandro Reis Date: Wed, 12 Jun 2024 16:42:49 -0300 Subject: [PATCH 23/43] feat: add currency-rate repository using remote API gateway - Created new repository class `CurrencyRateRepositoryApi` to manage currency rates. - Implemented methods to find, add, and delete currency rates. - Integrated the repository with the remote API gateway for fetching data. - Updated the `CurrencyApiGateway` to support filtering and fetching currencies and rates. - Added necessary imports and updated the application logic to use the new repository. --- .gitignore | 2 + package.json | 1 + .../use-case/create-currency-rate.ts | 2 +- src/domain/value-object/code.ts | 2 +- .../api.currencyapi-v3-currencies.json | 2162 +++++++++++++++++ .../gateway/api.currencyapi-v3-latest.json | 192 ++ .../gateway/currency-api-gateway-online.ts | 29 + .../gateway/currency-api-gateway-static.ts | 68 + src/infra/gateway/currency-api-gateway.ts | 38 + src/infra/http-client/axios-adapter.ts | 29 + src/infra/http-client/http-client.ts | 19 + src/infra/http-server/currency.controller.ts | 44 +- .../currency-rate-repository-api.ts | 49 + src/main/main-gateway.ts | 21 + test/integration/api.test.ts | 31 +- 15 files changed, 2666 insertions(+), 23 deletions(-) create mode 100644 src/infra/gateway/api.currencyapi-v3-currencies.json create mode 100644 src/infra/gateway/api.currencyapi-v3-latest.json create mode 100644 src/infra/gateway/currency-api-gateway-online.ts create mode 100644 src/infra/gateway/currency-api-gateway-static.ts create mode 100644 src/infra/gateway/currency-api-gateway.ts create mode 100644 src/infra/http-client/axios-adapter.ts create mode 100644 src/infra/http-client/http-client.ts create mode 100644 src/infra/repository/currency-rate-repository-api.ts create mode 100644 src/main/main-gateway.ts diff --git a/.gitignore b/.gitignore index 3c3629e64..6d3a831e2 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,3 @@ node_modules +coverage +.DS_Store \ No newline at end of file diff --git a/package.json b/package.json index ea8954c0c..06978489c 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,7 @@ "dev:express": "ts-node-dev -r tsconfig-paths/register src/main/main-express.ts", "dev:hapi": "ts-node-dev -r tsconfig-paths/register src/main/main-hapi.ts", "dev:express-pg": "ts-node-dev -r tsconfig-paths/register src/main/main-express-pg.ts", + "dev:gateway": "ts-node-dev -r tsconfig-paths/register src/main/main-gateway.ts", "test": "jest", "test:watch": "jest --watch --detectOpenHandles" }, diff --git a/src/application/use-case/create-currency-rate.ts b/src/application/use-case/create-currency-rate.ts index fe50a7956..3151fe9cb 100644 --- a/src/application/use-case/create-currency-rate.ts +++ b/src/application/use-case/create-currency-rate.ts @@ -6,7 +6,7 @@ export default class CreateCurrencyRate { constructor(private currencyRateRepository: CurrencyRateRepository) { } async execute(data: CreateCurrencyRateDto): Promise { - const equivalentCurrencyRate = await this.currencyRateRepository.findByCode(data.equivalentCurrencyCode); + const equivalentCurrencyRate = await this.currencyRateRepository.findByCode(data.equivalentCurrencyCode.toUpperCase()); const equivalentRateCalculator = new EquivalentRateCalculator(equivalentCurrencyRate.rate); const rate = equivalentRateCalculator.calculateRate(data.baseAmount, data.equivalentCurrencyAmount) const currencyRate = CurrencyRate.create(data.name, data.code, data.symbol, data.decimalPoints, rate); diff --git a/src/domain/value-object/code.ts b/src/domain/value-object/code.ts index e3664fb09..48466d526 100644 --- a/src/domain/value-object/code.ts +++ b/src/domain/value-object/code.ts @@ -5,7 +5,7 @@ export class Code { if (value.length < 3 || value.length > 4) { throw new Error('Invalid code'); } - this._value = value; + this._value = value.toUpperCase(); } get value() { diff --git a/src/infra/gateway/api.currencyapi-v3-currencies.json b/src/infra/gateway/api.currencyapi-v3-currencies.json new file mode 100644 index 000000000..d75d5c2ea --- /dev/null +++ b/src/infra/gateway/api.currencyapi-v3-currencies.json @@ -0,0 +1,2162 @@ +{ + "data": { + "AED": { + "symbol": "AED", + "name": "United Arab Emirates Dirham", + "symbol_native": "د.إ", + "decimal_digits": 2, + "rounding": 0, + "code": "AED", + "name_plural": "UAE dirhams", + "type": "fiat", + "countries": ["AE"] + }, + "AFN": { + "symbol": "Af", + "name": "Afghan Afghani", + "symbol_native": "؋", + "decimal_digits": 0, + "rounding": 0, + "code": "AFN", + "name_plural": "Afghan Afghanis", + "type": "fiat", + "countries": ["AF"] + }, + "ALL": { + "symbol": "ALL", + "name": "Albanian Lek", + "symbol_native": "Lek", + "decimal_digits": 0, + "rounding": 0, + "code": "ALL", + "name_plural": "Albanian lekë", + "type": "fiat", + "countries": ["AL"] + }, + "AMD": { + "symbol": "AMD", + "name": "Armenian Dram", + "symbol_native": "դր.", + "decimal_digits": 0, + "rounding": 0, + "code": "AMD", + "name_plural": "Armenian drams", + "type": "fiat", + "countries": ["AM"] + }, + "ANG": { + "symbol": "ƒ", + "name": "NL Antillean Guilder", + "symbol_native": "NAƒ", + "decimal_digits": 2, + "rounding": 0, + "code": "ANG", + "icon_name": "ang", + "name_plural": "NL Antillean Guilders", + "type": "fiat", + "countries": ["CW", "SX"] + }, + "AOA": { + "symbol": "Kz", + "name": "Angolan Kwanza", + "symbol_native": "Kz", + "decimal_digits": 2, + "rounding": 0, + "code": "AOA", + "icon_name": "aoa", + "name_plural": "Angolan Kwanza", + "type": "fiat", + "countries": ["AO"] + }, + "ARS": { + "symbol": "AR$", + "name": "Argentine Peso", + "symbol_native": "$", + "decimal_digits": 2, + "rounding": 0, + "code": "ARS", + "name_plural": "Argentine pesos", + "type": "fiat", + "countries": ["AR"] + }, + "AUD": { + "symbol": "AU$", + "name": "Australian Dollar", + "symbol_native": "$", + "decimal_digits": 2, + "rounding": 0, + "code": "AUD", + "name_plural": "Australian dollars", + "type": "fiat", + "countries": ["AU", "CC", "CX", "HM", "KI", "NF", "NR", "TV"] + }, + "AWG": { + "symbol": "Afl.", + "name": "Aruban Florin", + "symbol_native": "Afl.", + "decimal_digits": 2, + "rounding": 0, + "code": "AWG", + "icon_name": "awg", + "name_plural": "Aruban Florin", + "type": "fiat", + "countries": ["AW"] + }, + "AZN": { + "symbol": "man.", + "name": "Azerbaijani Manat", + "symbol_native": "ман.", + "decimal_digits": 2, + "rounding": 0, + "code": "AZN", + "name_plural": "Azerbaijani manats", + "type": "fiat", + "countries": ["AZ"] + }, + "BAM": { + "symbol": "KM", + "name": "Bosnia-Herzegovina Convertible Mark", + "symbol_native": "KM", + "decimal_digits": 2, + "rounding": 0, + "code": "BAM", + "name_plural": "Bosnia-Herzegovina convertible marks", + "type": "fiat", + "countries": ["BA"] + }, + "BBD": { + "symbol": "Bds$", + "name": "Barbadian Dollar", + "symbol_native": "$", + "decimal_digits": 2, + "rounding": 0, + "code": "BBD", + "icon_name": "bbd", + "name_plural": "Barbadian Dollars", + "type": "fiat", + "countries": ["BB"] + }, + "BDT": { + "symbol": "Tk", + "name": "Bangladeshi Taka", + "symbol_native": "৳", + "decimal_digits": 2, + "rounding": 0, + "code": "BDT", + "name_plural": "Bangladeshi takas", + "type": "fiat", + "countries": ["BD"] + }, + "BGN": { + "symbol": "BGN", + "name": "Bulgarian Lev", + "symbol_native": "лв.", + "decimal_digits": 2, + "rounding": 0, + "code": "BGN", + "name_plural": "Bulgarian leva", + "type": "fiat", + "countries": ["BG"] + }, + "BHD": { + "symbol": "BD", + "name": "Bahraini Dinar", + "symbol_native": "د.ب.‏", + "decimal_digits": 3, + "rounding": 0, + "code": "BHD", + "name_plural": "Bahraini dinars", + "type": "fiat", + "countries": ["BH"] + }, + "BIF": { + "symbol": "FBu", + "name": "Burundian Franc", + "symbol_native": "FBu", + "decimal_digits": 0, + "rounding": 0, + "code": "BIF", + "name_plural": "Burundian francs", + "type": "fiat", + "countries": ["BI"] + }, + "BMD": { + "symbol": "BD$", + "name": "Bermudan Dollar", + "symbol_native": "$", + "decimal_digits": 2, + "rounding": 0, + "code": "BMD", + "icon_name": "bmd", + "name_plural": "Bermudan Dollars", + "type": "fiat", + "countries": ["BM"] + }, + "BND": { + "symbol": "BN$", + "name": "Brunei Dollar", + "symbol_native": "$", + "decimal_digits": 2, + "rounding": 0, + "code": "BND", + "name_plural": "Brunei dollars", + "type": "fiat", + "countries": ["BN"] + }, + "BOB": { + "symbol": "Bs", + "name": "Bolivian Boliviano", + "symbol_native": "Bs", + "decimal_digits": 2, + "rounding": 0, + "code": "BOB", + "name_plural": "Bolivian bolivianos", + "type": "fiat", + "countries": ["BO"] + }, + "BRL": { + "symbol": "R$", + "name": "Brazilian Real", + "symbol_native": "R$", + "decimal_digits": 2, + "rounding": 0, + "code": "BRL", + "name_plural": "Brazilian reals", + "type": "fiat", + "countries": ["BR"] + }, + "BSD": { + "symbol": "B$", + "name": "Bahamian Dollar", + "symbol_native": "$", + "decimal_digits": 2, + "rounding": 0, + "code": "BSD", + "icon_name": "bsd", + "name_plural": "Bahamian Dollars", + "type": "fiat", + "countries": ["BS"] + }, + "BTN": { + "symbol": "Nu.", + "name": "Bhutanese Ngultrum", + "symbol_native": "Nu.", + "decimal_digits": 2, + "rounding": 0, + "code": "BTN", + "icon_name": "btn", + "name_plural": "Bhutanese Ngultrum", + "type": "fiat", + "countries": ["BT"] + }, + "BWP": { + "symbol": "BWP", + "name": "Botswanan Pula", + "symbol_native": "P", + "decimal_digits": 2, + "rounding": 0, + "code": "BWP", + "name_plural": "Botswanan pulas", + "type": "fiat", + "countries": ["BW", "ZW"] + }, + "BYN": { + "symbol": "Br", + "name": "Belarusian ruble", + "symbol_native": "Br", + "decimal_digits": 2, + "rounding": 0, + "code": "BYN", + "name_plural": "Belarusian rubles", + "type": "fiat", + "countries": [] + }, + "BYR": { + "symbol": "BYR", + "name": "Belarusian Ruble", + "symbol_native": "BYR", + "decimal_digits": 0, + "rounding": 0, + "code": "BYR", + "name_plural": "Belarusian rubles", + "type": "fiat", + "countries": ["BY"] + }, + "BZD": { + "symbol": "BZ$", + "name": "Belize Dollar", + "symbol_native": "$", + "decimal_digits": 2, + "rounding": 0, + "code": "BZD", + "name_plural": "Belize dollars", + "type": "fiat", + "countries": ["BZ"] + }, + "CAD": { + "symbol": "CA$", + "name": "Canadian Dollar", + "symbol_native": "$", + "decimal_digits": 2, + "rounding": 0, + "code": "CAD", + "name_plural": "Canadian dollars", + "type": "fiat", + "countries": ["CA"] + }, + "CDF": { + "symbol": "CDF", + "name": "Congolese Franc", + "symbol_native": "FrCD", + "decimal_digits": 2, + "rounding": 0, + "code": "CDF", + "name_plural": "Congolese francs", + "type": "fiat", + "countries": ["CD"] + }, + "CHF": { + "symbol": "CHF", + "name": "Swiss Franc", + "symbol_native": "CHF", + "decimal_digits": 2, + "rounding": 0, + "code": "CHF", + "name_plural": "Swiss francs", + "type": "fiat", + "countries": ["CH", "LI"] + }, + "CLF": { + "symbol": "UF", + "name": "Unidad de Fomento", + "symbol_native": "UF", + "decimal_digits": 2, + "rounding": 0, + "code": "CLF", + "name_plural": "Unidad de Fomentos", + "type": "fiat", + "countries": ["CL"] + }, + "CLP": { + "symbol": "CL$", + "name": "Chilean Peso", + "symbol_native": "$", + "decimal_digits": 0, + "rounding": 0, + "code": "CLP", + "name_plural": "Chilean pesos", + "type": "fiat", + "countries": ["CL"] + }, + "CNY": { + "symbol": "CN¥", + "name": "Chinese Yuan", + "symbol_native": "CN¥", + "decimal_digits": 2, + "rounding": 0, + "code": "CNY", + "name_plural": "Chinese yuan", + "type": "fiat", + "countries": ["CN"] + }, + "COP": { + "symbol": "CO$", + "name": "Coombian Peso", + "symbol_native": "$", + "decimal_digits": 0, + "rounding": 0, + "code": "COP", + "name_plural": "Colombian pesos", + "type": "fiat", + "countries": ["CO"] + }, + "CRC": { + "symbol": "₡", + "name": "Costa Rican Colón", + "symbol_native": "₡", + "decimal_digits": 0, + "rounding": 0, + "code": "CRC", + "name_plural": "Costa Rican colóns", + "type": "fiat", + "countries": ["CR"] + }, + "CUC": { + "symbol": "CUC$", + "name": "Cuban Convertible Peso", + "symbol_native": "$", + "decimal_digits": 2, + "rounding": 0, + "code": "CUC", + "icon_name": "cuc", + "name_plural": "Cuban Convertible Peso", + "type": "fiat", + "countries": ["CU"] + }, + "CUP": { + "symbol": "$MN", + "name": "Cuban Peso", + "symbol_native": "$", + "decimal_digits": 2, + "rounding": 0, + "code": "CUP", + "icon_name": "cup", + "name_plural": "Cuban Peso", + "type": "fiat", + "countries": ["CU"] + }, + "CVE": { + "symbol": "CV$", + "name": "Cape Verdean Escudo", + "symbol_native": "CV$", + "decimal_digits": 2, + "rounding": 0, + "code": "CVE", + "name_plural": "Cape Verdean escudos", + "type": "fiat", + "countries": ["CV"] + }, + "CZK": { + "symbol": "Kč", + "name": "Czech Republic Koruna", + "symbol_native": "Kč", + "decimal_digits": 2, + "rounding": 0, + "code": "CZK", + "name_plural": "Czech Republic korunas", + "type": "fiat", + "countries": ["CZ"] + }, + "DJF": { + "symbol": "Fdj", + "name": "Djiboutian Franc", + "symbol_native": "Fdj", + "decimal_digits": 0, + "rounding": 0, + "code": "DJF", + "name_plural": "Djiboutian francs", + "type": "fiat", + "countries": ["DJ"] + }, + "DKK": { + "symbol": "Dkr", + "name": "Danish Krone", + "symbol_native": "kr", + "decimal_digits": 2, + "rounding": 0, + "code": "DKK", + "name_plural": "Danish kroner", + "type": "fiat", + "countries": ["DK", "FO", "GL"] + }, + "DOP": { + "symbol": "RD$", + "name": "Dominican Peso", + "symbol_native": "RD$", + "decimal_digits": 2, + "rounding": 0, + "code": "DOP", + "name_plural": "Dominican pesos", + "type": "fiat", + "countries": ["DO"] + }, + "DZD": { + "symbol": "DA", + "name": "Algerian Dinar", + "symbol_native": "د.ج.‏", + "decimal_digits": 2, + "rounding": 0, + "code": "DZD", + "name_plural": "Algerian dinars", + "type": "fiat", + "countries": ["DZ"] + }, + "EGP": { + "symbol": "EGP", + "name": "Egyptian Pound", + "symbol_native": "ج.م.‏", + "decimal_digits": 2, + "rounding": 0, + "code": "EGP", + "name_plural": "Egyptian pounds", + "type": "fiat", + "countries": ["EG", "PS"] + }, + "ERN": { + "symbol": "Nfk", + "name": "Eritrean Nakfa", + "symbol_native": "Nfk", + "decimal_digits": 2, + "rounding": 0, + "code": "ERN", + "name_plural": "Eritrean nakfas", + "type": "fiat", + "countries": ["ER"] + }, + "ETB": { + "symbol": "Br", + "name": "Ethiopian Birr", + "symbol_native": "Br", + "decimal_digits": 2, + "rounding": 0, + "code": "ETB", + "name_plural": "Ethiopian birrs", + "type": "fiat", + "countries": ["ET"] + }, + "EUR": { + "symbol": "€", + "name": "Euro", + "symbol_native": "€", + "decimal_digits": 2, + "rounding": 0, + "code": "EUR", + "name_plural": "Euros", + "type": "fiat", + "countries": [ + "AD", + "AT", + "AX", + "BE", + "BL", + "CP", + "CY", + "DE", + "EA", + "EE", + "ES", + "EU", + "FI", + "FR", + "FX", + "GF", + "GP", + "GR", + "IC", + "IE", + "IT", + "LT", + "LU", + "LV", + "MC", + "ME", + "MF", + "MQ", + "MT", + "NL", + "PM", + "PT", + "RE", + "SI", + "SK", + "SM", + "TF", + "VA", + "XK", + "YT", + "ZW" + ] + }, + "FJD": { + "symbol": "FJ$", + "name": "Fijian Dollar", + "symbol_native": "$", + "decimal_digits": 2, + "rounding": 0, + "code": "FJD", + "icon_name": "fjd", + "name_plural": "Fijian Dollar", + "type": "fiat", + "countries": ["FJ"] + }, + "FKP": { + "symbol": "FK£", + "name": "Falkland Islands Pound", + "symbol_native": "£", + "decimal_digits": 2, + "rounding": 0, + "code": "FKP", + "icon_name": "fkp", + "name_plural": "Falkland Islands Pound", + "type": "fiat", + "countries": ["FK"] + }, + "GBP": { + "symbol": "£", + "name": "British Pound Sterling", + "symbol_native": "£", + "decimal_digits": 2, + "rounding": 0, + "code": "GBP", + "name_plural": "British pounds sterling", + "type": "fiat", + "countries": ["GB", "GG", "GS", "IM", "JE", "TA", "UK", "ZW"] + }, + "GEL": { + "symbol": "GEL", + "name": "Georgian Lari", + "symbol_native": "GEL", + "decimal_digits": 2, + "rounding": 0, + "code": "GEL", + "name_plural": "Georgian laris", + "type": "fiat", + "countries": ["GE"] + }, + "GGP": { + "symbol": "£", + "name": "Guernsey pound", + "symbol_native": "£", + "decimal_digits": 2, + "rounding": 0, + "code": "GGP", + "name_plural": "Guernsey pounds", + "type": "fiat", + "countries": [] + }, + "GHS": { + "symbol": "GH₵", + "name": "Ghanaian Cedi", + "symbol_native": "GH₵", + "decimal_digits": 2, + "rounding": 0, + "code": "GHS", + "name_plural": "Ghanaian cedis", + "type": "fiat", + "countries": ["GH"] + }, + "GIP": { + "symbol": "£", + "name": "Gibraltar Pound", + "symbol_native": "£", + "decimal_digits": 2, + "rounding": 0, + "code": "GIP", + "icon_name": "gip", + "name_plural": "Gibraltar Pounds", + "type": "fiat", + "countries": ["GI"] + }, + "GMD": { + "symbol": "D", + "name": "Gambian Dalasi", + "symbol_native": "D", + "decimal_digits": 2, + "rounding": 0, + "code": "GMD", + "icon_name": "gmd", + "name_plural": "Gambian Dalasi", + "type": "fiat", + "countries": ["GM"] + }, + "GNF": { + "symbol": "FG", + "name": "Guinean Franc", + "symbol_native": "FG", + "decimal_digits": 0, + "rounding": 0, + "code": "GNF", + "name_plural": "Guinean francs", + "type": "fiat", + "countries": ["GN"] + }, + "GTQ": { + "symbol": "GTQ", + "name": "Guatemalan Quetzal", + "symbol_native": "Q", + "decimal_digits": 2, + "rounding": 0, + "code": "GTQ", + "name_plural": "Guatemalan quetzals", + "type": "fiat", + "countries": ["GT"] + }, + "GYD": { + "symbol": "G$", + "name": "Guyanaese Dollar", + "symbol_native": "$", + "decimal_digits": 2, + "rounding": 0, + "code": "GYD", + "icon_name": "gyd", + "name_plural": "Guyanaese Dollar", + "type": "fiat", + "countries": ["GY"] + }, + "HKD": { + "symbol": "HK$", + "name": "Hong Kong Dollar", + "symbol_native": "$", + "decimal_digits": 2, + "rounding": 0, + "code": "HKD", + "name_plural": "Hong Kong dollars", + "type": "fiat", + "countries": ["HK"] + }, + "HNL": { + "symbol": "HNL", + "name": "Honduran Lempira", + "symbol_native": "L", + "decimal_digits": 2, + "rounding": 0, + "code": "HNL", + "name_plural": "Honduran lempiras", + "type": "fiat", + "countries": ["HN"] + }, + "HRK": { + "symbol": "kn", + "name": "Croatian Kuna", + "symbol_native": "kn", + "decimal_digits": 2, + "rounding": 0, + "code": "HRK", + "name_plural": "Croatian kunas", + "type": "fiat", + "countries": ["HR"] + }, + "HTG": { + "symbol": "G", + "name": "Haitian Gourde", + "symbol_native": "G", + "decimal_digits": 2, + "rounding": 0, + "code": "HTG", + "icon_name": "htg", + "name_plural": "Haitian Gourde", + "type": "fiat", + "countries": ["HT"] + }, + "HUF": { + "symbol": "Ft", + "name": "Hungarian Forint", + "symbol_native": "Ft", + "decimal_digits": 0, + "rounding": 0, + "code": "HUF", + "name_plural": "Hungarian forints", + "type": "fiat", + "countries": ["HU"] + }, + "IDR": { + "symbol": "Rp", + "name": "Indonesian Rupiah", + "symbol_native": "Rp", + "decimal_digits": 0, + "rounding": 0, + "code": "IDR", + "name_plural": "Indonesian rupiahs", + "type": "fiat", + "countries": ["ID"] + }, + "ILS": { + "symbol": "₪", + "name": "Israeli New Sheqel", + "symbol_native": "₪", + "decimal_digits": 2, + "rounding": 0, + "code": "ILS", + "name_plural": "Israeli new sheqels", + "type": "fiat", + "countries": ["IL", "PS"] + }, + "IMP": { + "symbol": "£", + "name": "Manx pound", + "symbol_native": "£", + "decimal_digits": 2, + "rounding": 0, + "code": "IMP", + "name_plural": "Manx pounds", + "type": "fiat", + "countries": [] + }, + "INR": { + "symbol": "Rs", + "name": "Indian Rupee", + "symbol_native": "টকা", + "decimal_digits": 2, + "rounding": 0, + "code": "INR", + "name_plural": "Indian rupees", + "type": "fiat", + "countries": ["BT", "IN"] + }, + "IQD": { + "symbol": "IQD", + "name": "Iraqi Dinar", + "symbol_native": "د.ع.‏", + "decimal_digits": 0, + "rounding": 0, + "code": "IQD", + "name_plural": "Iraqi dinars", + "type": "fiat", + "countries": ["IQ"] + }, + "IRR": { + "symbol": "IRR", + "name": "Iranian Rial", + "symbol_native": "﷼", + "decimal_digits": 0, + "rounding": 0, + "code": "IRR", + "name_plural": "Iranian rials", + "type": "fiat", + "countries": ["IR"] + }, + "ISK": { + "symbol": "Ikr", + "name": "Icelandic Króna", + "symbol_native": "kr", + "decimal_digits": 0, + "rounding": 0, + "code": "ISK", + "name_plural": "Icelandic krónur", + "type": "fiat", + "countries": ["IS"] + }, + "JEP": { + "symbol": "£", + "name": "Jersey pound", + "symbol_native": "£", + "decimal_digits": 2, + "rounding": 0, + "code": "JEP", + "name_plural": "Jersey pound", + "type": "fiat", + "countries": [] + }, + "JMD": { + "symbol": "J$", + "name": "Jamaican Dollar", + "symbol_native": "$", + "decimal_digits": 2, + "rounding": 0, + "code": "JMD", + "name_plural": "Jamaican dollars", + "type": "fiat", + "countries": ["JM"] + }, + "JOD": { + "symbol": "JD", + "name": "Jordanian Dinar", + "symbol_native": "د.أ.‏", + "decimal_digits": 3, + "rounding": 0, + "code": "JOD", + "name_plural": "Jordanian dinars", + "type": "fiat", + "countries": ["JO", "PS"] + }, + "JPY": { + "symbol": "¥", + "name": "Japanese Yen", + "symbol_native": "¥", + "decimal_digits": 0, + "rounding": 0, + "code": "JPY", + "name_plural": "Japanese yen", + "type": "fiat", + "countries": ["JP"] + }, + "KES": { + "symbol": "Ksh", + "name": "Kenyan Shilling", + "symbol_native": "Ksh", + "decimal_digits": 2, + "rounding": 0, + "code": "KES", + "name_plural": "Kenyan shillings", + "type": "fiat", + "countries": ["KE"] + }, + "KGS": { + "symbol": "KGS", + "name": "Kyrgystani Som", + "symbol_native": "KGS", + "decimal_digits": 2, + "rounding": 0, + "code": "KGS", + "icon_name": "kgs", + "name_plural": "Kyrgystani Som", + "type": "fiat", + "countries": ["KG"] + }, + "KHR": { + "symbol": "KHR", + "name": "Cambodian Riel", + "symbol_native": "៛", + "decimal_digits": 2, + "rounding": 0, + "code": "KHR", + "name_plural": "Cambodian riels", + "type": "fiat", + "countries": ["KH"] + }, + "KMF": { + "symbol": "CF", + "name": "Comorian Franc", + "symbol_native": "FC", + "decimal_digits": 0, + "rounding": 0, + "code": "KMF", + "name_plural": "Comorian francs", + "type": "fiat", + "countries": ["KM"] + }, + "KPW": { + "symbol": "₩", + "name": "North Korean Won", + "symbol_native": "₩", + "decimal_digits": 2, + "rounding": 0, + "code": "KPW", + "icon_name": "kpw", + "name_plural": "North Korean Won", + "type": "fiat", + "countries": ["KP"] + }, + "KRW": { + "symbol": "₩", + "name": "South Korean Won", + "symbol_native": "₩", + "decimal_digits": 0, + "rounding": 0, + "code": "KRW", + "name_plural": "South Korean won", + "type": "fiat", + "countries": ["KR"] + }, + "KWD": { + "symbol": "KD", + "name": "Kuwaiti Dinar", + "symbol_native": "د.ك.‏", + "decimal_digits": 3, + "rounding": 0, + "code": "KWD", + "name_plural": "Kuwaiti dinars", + "type": "fiat", + "countries": ["KW"] + }, + "KYD": { + "symbol": "CI$", + "name": "Cayman Islands Dollar", + "symbol_native": "$‏", + "decimal_digits": 2, + "rounding": 0, + "code": "KYD", + "icon_name": "kyd", + "name_plural": "Cayman Islands Dollar", + "type": "fiat", + "countries": ["KY"] + }, + "KZT": { + "symbol": "KZT", + "name": "Kazakhstani Tenge", + "symbol_native": "тңг.", + "decimal_digits": 2, + "rounding": 0, + "code": "KZT", + "name_plural": "Kazakhstani tenges", + "type": "fiat", + "countries": ["KZ"] + }, + "LAK": { + "symbol": "₭N", + "name": "Laotian Kip", + "symbol_native": "₭‏‏", + "decimal_digits": 0, + "rounding": 0, + "code": "LAK", + "name_plural": "Laotian Kip", + "type": "fiat", + "countries": ["LA"] + }, + "LBP": { + "symbol": "LB£", + "name": "Lebanese Pound", + "symbol_native": "ل.ل.‏", + "decimal_digits": 0, + "rounding": 0, + "code": "LBP", + "name_plural": "Lebanese pounds", + "type": "fiat", + "countries": ["LB"] + }, + "LKR": { + "symbol": "SLRs", + "name": "Sri Lankan Rupee", + "symbol_native": "SL Re", + "decimal_digits": 2, + "rounding": 0, + "code": "LKR", + "name_plural": "Sri Lankan rupees", + "type": "fiat", + "countries": ["LK"] + }, + "LRD": { + "symbol": "LD$", + "name": "Liberian Dollar", + "symbol_native": "L$", + "decimal_digits": 2, + "rounding": 0, + "code": "LRD", + "icon_name": "lrd", + "name_plural": "Liberian Dollar", + "type": "fiat", + "countries": ["LR"] + }, + "LSL": { + "symbol": "L", + "name": "Lesotho Loti", + "symbol_native": "M", + "decimal_digits": 2, + "rounding": 0, + "code": "LSL", + "icon_name": "lsl", + "name_plural": "Lesotho Loti", + "type": "fiat", + "countries": ["LS"] + }, + "LTL": { + "symbol": "Lt", + "name": "Lithuanian Litas", + "symbol_native": "Lt", + "decimal_digits": 2, + "rounding": 0, + "code": "LTL", + "name_plural": "Lithuanian litai", + "type": "fiat", + "countries": [] + }, + "LVL": { + "symbol": "Ls", + "name": "Latvian Lats", + "symbol_native": "Ls", + "decimal_digits": 2, + "rounding": 0, + "code": "LVL", + "name_plural": "Latvian lati", + "type": "fiat", + "countries": [] + }, + "LYD": { + "symbol": "LD", + "name": "Libyan Dinar", + "symbol_native": "د.ل.‏", + "decimal_digits": 3, + "rounding": 0, + "code": "LYD", + "name_plural": "Libyan dinars", + "type": "fiat", + "countries": ["LY"] + }, + "MAD": { + "symbol": "MAD", + "name": "Moroccan Dirham", + "symbol_native": "د.م.‏", + "decimal_digits": 2, + "rounding": 0, + "code": "MAD", + "name_plural": "Moroccan dirhams", + "type": "fiat", + "countries": ["EH", "MA"] + }, + "MDL": { + "symbol": "MDL", + "name": "Moldovan Leu", + "symbol_native": "MDL", + "decimal_digits": 2, + "rounding": 0, + "code": "MDL", + "name_plural": "Moldovan lei", + "type": "fiat", + "countries": ["MD"] + }, + "MGA": { + "symbol": "MGA", + "name": "Malagasy Ariary", + "symbol_native": "MGA", + "decimal_digits": 0, + "rounding": 0, + "code": "MGA", + "name_plural": "Malagasy Ariaries", + "type": "fiat", + "countries": ["MG"] + }, + "MKD": { + "symbol": "MKD", + "name": "Macedonian Denar", + "symbol_native": "MKD", + "decimal_digits": 2, + "rounding": 0, + "code": "MKD", + "name_plural": "Macedonian denari", + "type": "fiat", + "countries": ["MK"] + }, + "MMK": { + "symbol": "MMK", + "name": "Myanma Kyat", + "symbol_native": "K", + "decimal_digits": 0, + "rounding": 0, + "code": "MMK", + "name_plural": "Myanma kyats", + "type": "fiat", + "countries": ["MM"] + }, + "MNT": { + "symbol": "₮", + "name": "Mongolian Tugrik", + "symbol_native": "₮", + "decimal_digits": 2, + "rounding": 0, + "code": "MNT", + "icon_name": "mnt", + "name_plural": "Mongolian Tugrik", + "type": "fiat", + "countries": ["MN"] + }, + "MOP": { + "symbol": "MOP$", + "name": "Macanese Pataca", + "symbol_native": "MOP$", + "decimal_digits": 2, + "rounding": 0, + "code": "MOP", + "name_plural": "Macanese patacas", + "type": "fiat", + "countries": ["MO"] + }, + "MRO": { + "symbol": "UM", + "name": "Mauritanian ouguiya", + "symbol_native": "UM", + "decimal_digits": 2, + "rounding": 0, + "code": "MRO", + "name_plural": "Mauritanian ouguiyas", + "type": "fiat", + "countries": ["MR"] + }, + "MUR": { + "symbol": "MURs", + "name": "Mauritian Rupee", + "symbol_native": "MURs", + "decimal_digits": 0, + "rounding": 0, + "code": "MUR", + "name_plural": "Mauritian rupees", + "type": "fiat", + "countries": ["MU"] + }, + "MVR": { + "symbol": "MRf", + "name": "Maldivian Rufiyaa", + "symbol_native": "Rf", + "decimal_digits": 2, + "rounding": 0, + "code": "MVR", + "name_plural": "Maldivian Rufiyaa", + "type": "fiat", + "countries": ["MV"] + }, + "MWK": { + "symbol": "MK", + "name": "Malawian Kwacha", + "symbol_native": "MK", + "decimal_digits": 2, + "rounding": 0, + "code": "MWK", + "icon_name": "mwk", + "name_plural": "Malawian Kwacha", + "type": "fiat", + "countries": ["MW"] + }, + "MXN": { + "symbol": "MX$", + "name": "Mexican Peso", + "symbol_native": "$", + "decimal_digits": 2, + "rounding": 0, + "code": "MXN", + "name_plural": "Mexican pesos", + "type": "fiat", + "countries": ["MX"] + }, + "MYR": { + "symbol": "RM", + "name": "Malaysian Ringgit", + "symbol_native": "RM", + "decimal_digits": 2, + "rounding": 0, + "code": "MYR", + "name_plural": "Malaysian ringgits", + "type": "fiat", + "countries": ["MY"] + }, + "MZN": { + "symbol": "MTn", + "name": "Mozambican Metical", + "symbol_native": "MTn", + "decimal_digits": 2, + "rounding": 0, + "code": "MZN", + "name_plural": "Mozambican meticals", + "type": "fiat", + "countries": ["MZ"] + }, + "NAD": { + "symbol": "N$", + "name": "Namibian Dollar", + "symbol_native": "N$", + "decimal_digits": 2, + "rounding": 0, + "code": "NAD", + "name_plural": "Namibian dollars", + "type": "fiat", + "countries": ["NA"] + }, + "NGN": { + "symbol": "₦", + "name": "Nigerian Naira", + "symbol_native": "₦", + "decimal_digits": 2, + "rounding": 0, + "code": "NGN", + "name_plural": "Nigerian nairas", + "type": "fiat", + "countries": ["NG"] + }, + "NIO": { + "symbol": "C$", + "name": "Nicaraguan Córdoba", + "symbol_native": "C$", + "decimal_digits": 2, + "rounding": 0, + "code": "NIO", + "name_plural": "Nicaraguan córdobas", + "type": "fiat", + "countries": ["NI"] + }, + "NOK": { + "symbol": "Nkr", + "name": "Norwegian Krone", + "symbol_native": "kr", + "decimal_digits": 2, + "rounding": 0, + "code": "NOK", + "name_plural": "Norwegian kroner", + "type": "fiat", + "countries": ["BV", "NO", "SJ"] + }, + "NPR": { + "symbol": "NPRs", + "name": "Nepalese Rupee", + "symbol_native": "नेरू", + "decimal_digits": 2, + "rounding": 0, + "code": "NPR", + "name_plural": "Nepalese rupees", + "type": "fiat", + "countries": ["NP"] + }, + "NZD": { + "symbol": "NZ$", + "name": "New Zealand Dollar", + "symbol_native": "$", + "decimal_digits": 2, + "rounding": 0, + "code": "NZD", + "name_plural": "New Zealand dollars", + "type": "fiat", + "countries": ["CK", "NU", "NZ", "PN", "TK"] + }, + "OMR": { + "symbol": "OMR", + "name": "Omani Rial", + "symbol_native": "ر.ع.‏", + "decimal_digits": 3, + "rounding": 0, + "code": "OMR", + "name_plural": "Omani rials", + "type": "fiat", + "countries": ["OM"] + }, + "PAB": { + "symbol": "B/.", + "name": "Panamanian Balboa", + "symbol_native": "B/.", + "decimal_digits": 2, + "rounding": 0, + "code": "PAB", + "name_plural": "Panamanian balboas", + "type": "fiat", + "countries": ["PA"] + }, + "PEN": { + "symbol": "S/.", + "name": "Peruvian Nuevo Sol", + "symbol_native": "S/.", + "decimal_digits": 2, + "rounding": 0, + "code": "PEN", + "name_plural": "Peruvian nuevos soles", + "type": "fiat", + "countries": ["PE"] + }, + "PGK": { + "symbol": "K", + "name": "Papua New Guinean Kina", + "symbol_native": "K", + "decimal_digits": 2, + "rounding": 0, + "code": "PGK", + "icon_name": "pgk", + "name_plural": "Papua New Guinean Kina", + "type": "fiat", + "countries": ["PG"] + }, + "PHP": { + "symbol": "₱", + "name": "Philippine Peso", + "symbol_native": "₱", + "decimal_digits": 2, + "rounding": 0, + "code": "PHP", + "name_plural": "Philippine pesos", + "type": "fiat", + "countries": ["PH"] + }, + "PKR": { + "symbol": "PKRs", + "name": "Pakistani Rupee", + "symbol_native": "₨", + "decimal_digits": 0, + "rounding": 0, + "code": "PKR", + "name_plural": "Pakistani rupees", + "type": "fiat", + "countries": ["PK"] + }, + "PLN": { + "symbol": "zł", + "name": "Polish Zloty", + "symbol_native": "zł", + "decimal_digits": 2, + "rounding": 0, + "code": "PLN", + "name_plural": "Polish zlotys", + "type": "fiat", + "countries": ["PL"] + }, + "PYG": { + "symbol": "₲", + "name": "Paraguayan Guarani", + "symbol_native": "₲", + "decimal_digits": 0, + "rounding": 0, + "code": "PYG", + "name_plural": "Paraguayan guaranis", + "type": "fiat", + "countries": ["PY"] + }, + "QAR": { + "symbol": "QR", + "name": "Qatari Rial", + "symbol_native": "ر.ق.‏", + "decimal_digits": 2, + "rounding": 0, + "code": "QAR", + "name_plural": "Qatari rials", + "type": "fiat", + "countries": ["QA"] + }, + "RON": { + "symbol": "RON", + "name": "Romanian Leu", + "symbol_native": "RON", + "decimal_digits": 2, + "rounding": 0, + "code": "RON", + "name_plural": "Romanian lei", + "type": "fiat", + "countries": ["RO"] + }, + "RSD": { + "symbol": "din.", + "name": "Serbian Dinar", + "symbol_native": "дин.", + "decimal_digits": 0, + "rounding": 0, + "code": "RSD", + "name_plural": "Serbian dinars", + "type": "fiat", + "countries": ["RS"] + }, + "RUB": { + "symbol": "RUB", + "name": "Russian Ruble", + "symbol_native": "руб.", + "decimal_digits": 2, + "rounding": 0, + "code": "RUB", + "name_plural": "Russian rubles", + "type": "fiat", + "countries": ["RU", "SU"] + }, + "RWF": { + "symbol": "RWF", + "name": "Rwandan Franc", + "symbol_native": "FR", + "decimal_digits": 0, + "rounding": 0, + "code": "RWF", + "name_plural": "Rwandan francs", + "type": "fiat", + "countries": ["RW"] + }, + "SAR": { + "symbol": "SR", + "name": "Saudi Riyal", + "symbol_native": "ر.س.‏", + "decimal_digits": 2, + "rounding": 0, + "code": "SAR", + "name_plural": "Saudi riyals", + "type": "fiat", + "countries": ["SA"] + }, + "SBD": { + "symbol": "SI$", + "name": "Solomon Islands Dollar", + "symbol_native": "$", + "decimal_digits": 2, + "rounding": 0, + "code": "SBD", + "icon_name": "sbd", + "name_plural": "Solomon Islands Dollars", + "type": "fiat", + "countries": ["SB"] + }, + "SCR": { + "symbol": "SRe", + "name": "Seychellois Rupee", + "symbol_native": "SR", + "decimal_digits": 2, + "rounding": 0, + "code": "SCR", + "icon_name": "scr", + "name_plural": "Seychellois Rupees", + "type": "fiat", + "countries": ["SC"] + }, + "SDG": { + "symbol": "SDG", + "name": "Sudanese Pound", + "symbol_native": "SDG", + "decimal_digits": 2, + "rounding": 0, + "code": "SDG", + "name_plural": "Sudanese pounds", + "type": "fiat", + "countries": ["SD"] + }, + "SEK": { + "symbol": "Skr", + "name": "Swedish Krona", + "symbol_native": "kr", + "decimal_digits": 2, + "rounding": 0, + "code": "SEK", + "name_plural": "Swedish kronor", + "type": "fiat", + "countries": ["SE"] + }, + "SGD": { + "symbol": "S$", + "name": "Singapore Dollar", + "symbol_native": "$", + "decimal_digits": 2, + "rounding": 0, + "code": "SGD", + "name_plural": "Singapore dollars", + "type": "fiat", + "countries": ["SG"] + }, + "SHP": { + "symbol": "£", + "name": "Saint Helena Pound", + "symbol_native": "£", + "decimal_digits": 2, + "rounding": 0, + "code": "SHP", + "icon_name": "shp", + "name_plural": "Saint Helena Pounds", + "type": "fiat", + "countries": ["SH"] + }, + "SLL": { + "symbol": "Le", + "name": "Sierra Leonean Leone", + "symbol_native": "Le", + "decimal_digits": 2, + "rounding": 0, + "code": "SLL", + "icon_name": "sll", + "name_plural": "Sierra Leonean Leone", + "type": "fiat", + "countries": ["SL"] + }, + "SOS": { + "symbol": "Ssh", + "name": "Somali Shilling", + "symbol_native": "Ssh", + "decimal_digits": 0, + "rounding": 0, + "code": "SOS", + "name_plural": "Somali shillings", + "type": "fiat", + "countries": ["SO"] + }, + "SRD": { + "symbol": "$", + "name": "Surinamese Dollar", + "symbol_native": "$", + "decimal_digits": 2, + "rounding": 0, + "code": "SRD", + "icon_name": "srd", + "name_plural": "Surinamese Dollar", + "type": "fiat", + "countries": ["SR"] + }, + "STD": { + "symbol": "Db", + "name": "São Tomé and Príncipe dobra", + "symbol_native": "Db", + "decimal_digits": 2, + "rounding": 0, + "code": "STD", + "name_plural": "São Tomé and Príncipe dobras", + "type": "fiat", + "countries": ["ST"] + }, + "SVC": { + "symbol": "₡", + "name": "Salvadoran Colón", + "symbol_native": "₡", + "decimal_digits": 2, + "rounding": 0, + "code": "SVC", + "icon_name": "svc", + "name_plural": "Salvadoran Colón", + "type": "fiat", + "countries": [] + }, + "SYP": { + "symbol": "SY£", + "name": "Syrian Pound", + "symbol_native": "ل.س.‏", + "decimal_digits": 0, + "rounding": 0, + "code": "SYP", + "name_plural": "Syrian pounds", + "type": "fiat", + "countries": ["SY"] + }, + "SZL": { + "symbol": "L", + "name": "Swazi Lilangeni", + "symbol_native": "E‏", + "decimal_digits": 2, + "rounding": 0, + "code": "SZL", + "icon_name": "szl", + "name_plural": "Swazi Lilangeni", + "type": "fiat", + "countries": ["SZ"] + }, + "THB": { + "symbol": "฿", + "name": "Thai Baht", + "symbol_native": "฿", + "decimal_digits": 2, + "rounding": 0, + "code": "THB", + "name_plural": "Thai baht", + "type": "fiat", + "countries": ["TH"] + }, + "TJS": { + "symbol": "TJS", + "name": "Tajikistani Somoni", + "symbol_native": "TJS", + "decimal_digits": 2, + "rounding": 0, + "code": "TJS", + "icon_name": "tjs", + "name_plural": "Tajikistani Somoni", + "type": "fiat", + "countries": ["TJ"] + }, + "TMT": { + "symbol": "T", + "name": "Turkmenistani Manat", + "symbol_native": "T‏", + "decimal_digits": 2, + "rounding": 0, + "code": "TMT", + "icon_name": "tmt", + "name_plural": "Turkmenistani Manat", + "type": "fiat", + "countries": ["TM"] + }, + "TND": { + "symbol": "DT", + "name": "Tunisian Dinar", + "symbol_native": "د.ت.‏", + "decimal_digits": 3, + "rounding": 0, + "code": "TND", + "name_plural": "Tunisian dinars", + "type": "fiat", + "countries": ["TN"] + }, + "TOP": { + "symbol": "T$", + "name": "Tongan Paʻanga", + "symbol_native": "T$", + "decimal_digits": 2, + "rounding": 0, + "code": "TOP", + "name_plural": "Tongan paʻanga", + "type": "fiat", + "countries": ["TO"] + }, + "TRY": { + "symbol": "TL", + "name": "Turkish Lira", + "symbol_native": "TL", + "decimal_digits": 2, + "rounding": 0, + "code": "TRY", + "name_plural": "Turkish Lira", + "type": "fiat", + "countries": ["TR"] + }, + "TTD": { + "symbol": "TT$", + "name": "Trinidad and Tobago Dollar", + "symbol_native": "$", + "decimal_digits": 2, + "rounding": 0, + "code": "TTD", + "name_plural": "Trinidad and Tobago dollars", + "type": "fiat", + "countries": ["TT"] + }, + "TWD": { + "symbol": "NT$", + "name": "New Taiwan Dollar", + "symbol_native": "NT$", + "decimal_digits": 2, + "rounding": 0, + "code": "TWD", + "name_plural": "New Taiwan dollars", + "type": "fiat", + "countries": ["TW"] + }, + "TZS": { + "symbol": "TSh", + "name": "Tanzanian Shilling", + "symbol_native": "TSh", + "decimal_digits": 0, + "rounding": 0, + "code": "TZS", + "name_plural": "Tanzanian shillings", + "type": "fiat", + "countries": ["TZ"] + }, + "UAH": { + "symbol": "₴", + "name": "Ukrainian Hryvnia", + "symbol_native": "₴", + "decimal_digits": 2, + "rounding": 0, + "code": "UAH", + "name_plural": "Ukrainian hryvnias", + "type": "fiat", + "countries": ["UA"] + }, + "UGX": { + "symbol": "USh", + "name": "Ugandan Shilling", + "symbol_native": "USh", + "decimal_digits": 0, + "rounding": 0, + "code": "UGX", + "name_plural": "Ugandan shillings", + "type": "fiat", + "countries": ["UG"] + }, + "USD": { + "symbol": "$", + "name": "US Dollar", + "symbol_native": "$", + "decimal_digits": 2, + "rounding": 0, + "code": "USD", + "name_plural": "US dollars", + "type": "fiat", + "countries": [ + "AC", + "AS", + "BQ", + "DG", + "EC", + "FM", + "GU", + "HT", + "IO", + "MH", + "MP", + "PA", + "PR", + "PW", + "SV", + "TC", + "TL", + "UM", + "US", + "VG", + "VI", + "ZW" + ] + }, + "UYU": { + "symbol": "$U", + "name": "Uruguayan Peso", + "symbol_native": "$", + "decimal_digits": 2, + "rounding": 0, + "code": "UYU", + "name_plural": "Uruguayan pesos", + "type": "fiat", + "countries": ["UY"] + }, + "UZS": { + "symbol": "UZS", + "name": "Uzbekistan Som", + "symbol_native": "UZS", + "decimal_digits": 0, + "rounding": 0, + "code": "UZS", + "name_plural": "Uzbekistan som", + "type": "fiat", + "countries": ["UZ"] + }, + "VEF": { + "symbol": "Bs.F.", + "name": "Venezuelan Bolívar", + "symbol_native": "Bs.F.", + "decimal_digits": 2, + "rounding": 0, + "code": "VEF", + "name_plural": "Venezuelan bolívars", + "type": "fiat", + "countries": ["VE"] + }, + "VND": { + "symbol": "₫", + "name": "Vietnamese Dong", + "symbol_native": "₫", + "decimal_digits": 0, + "rounding": 0, + "code": "VND", + "name_plural": "Vietnamese dong", + "type": "fiat", + "countries": ["VN"] + }, + "VUV": { + "symbol": "VUV", + "name": "Vanuatu Vatu", + "symbol_native": "VT", + "decimal_digits": 0, + "rounding": 0, + "code": "VUV", + "icon_name": "vuv", + "name_plural": "Vanuatu Vatu", + "type": "fiat", + "countries": ["VU"] + }, + "WST": { + "symbol": "WS$", + "name": "Samoan Tala", + "symbol_native": "T", + "decimal_digits": 2, + "rounding": 0, + "code": "WST", + "icon_name": "wst", + "name_plural": "Samoan Tala", + "type": "fiat", + "countries": ["WS"] + }, + "XAF": { + "symbol": "FCFA", + "name": "CFA Franc BEAC", + "symbol_native": "FCFA", + "decimal_digits": 0, + "rounding": 0, + "code": "XAF", + "name_plural": "CFA francs BEAC", + "type": "fiat", + "countries": ["CF", "CG", "CM", "GA", "GQ", "TD"] + }, + "XAG": { + "symbol": "XAG", + "name": "Silver Ounce", + "symbol_native": "XAG", + "decimal_digits": 2, + "rounding": 0, + "code": "XAG", + "name_plural": "Silver Ounces", + "type": "metal", + "countries": [] + }, + "XAU": { + "symbol": "XAU", + "name": "Gold Ounce", + "symbol_native": "XAU", + "decimal_digits": 2, + "rounding": 0, + "code": "XAU", + "name_plural": "Gold Ounces", + "type": "metal", + "countries": [] + }, + "XCD": { + "symbol": "EC$", + "name": "East Caribbean Dollar", + "symbol_native": "$", + "decimal_digits": 2, + "rounding": 0, + "code": "XCD", + "icon_name": "xcd", + "name_plural": "East Caribbean Dollars", + "type": "fiat", + "countries": ["AG", "AI", "DM", "GD", "KN", "LC", "MS", "VC"] + }, + "XDR": { + "symbol": "SDR", + "name": "Special drawing rights", + "symbol_native": "SDR", + "decimal_digits": 2, + "rounding": 0, + "code": "XDR", + "name_plural": "Special drawing rights", + "type": "fiat", + "countries": [] + }, + "XOF": { + "symbol": "CFA", + "name": "CFA Franc BCEAO", + "symbol_native": "CFA", + "decimal_digits": 0, + "rounding": 0, + "code": "XOF", + "name_plural": "CFA francs BCEAO", + "type": "fiat", + "countries": ["BF", "BJ", "CI", "GW", "ML", "NE", "SN", "TG"] + }, + "XPF": { + "symbol": "CFP", + "name": "CFP Franc", + "symbol_native": "CFP", + "decimal_digits": 0, + "rounding": 0, + "code": "XPF", + "icon_name": "xpf", + "name_plural": "CFP francs", + "type": "fiat", + "countries": ["NC", "PF", "WF"] + }, + "YER": { + "symbol": "YR", + "name": "Yemeni Rial", + "symbol_native": "ر.ي.‏", + "decimal_digits": 0, + "rounding": 0, + "code": "YER", + "name_plural": "Yemeni rials", + "type": "fiat", + "countries": ["YE"] + }, + "ZAR": { + "symbol": "R", + "name": "South African Rand", + "symbol_native": "R", + "decimal_digits": 2, + "rounding": 0, + "code": "ZAR", + "name_plural": "South African rand", + "type": "fiat", + "countries": ["LS", "NA", "ZA", "ZW"] + }, + "ZMK": { + "symbol": "ZK", + "name": "Zambian Kwacha", + "symbol_native": "ZK", + "decimal_digits": 0, + "rounding": 0, + "code": "ZMK", + "name_plural": "Zambian kwachas", + "type": "fiat", + "countries": [] + }, + "ZMW": { + "symbol": "ZK", + "name": "Zambian Kwacha", + "symbol_native": "ZK", + "decimal_digits": 0, + "rounding": 0, + "code": "ZMW", + "name_plural": "Zambian kwachas", + "type": "fiat", + "countries": ["ZM"] + }, + "ZWL": { + "symbol": "ZWL", + "name": "Zimbabwean dollar", + "symbol_native": "$", + "decimal_digits": 2, + "rounding": 0, + "code": "ZWL", + "name_plural": "Zimbabwean dollars", + "type": "fiat", + "countries": [] + }, + "XPT": { + "symbol": "XPT", + "name": "Platinum Ounce", + "symbol_native": "XPT", + "decimal_digits": 6, + "rounding": 0, + "code": "XPT", + "name_plural": "Platinum Ounces", + "type": "metal", + "countries": [] + }, + "XPD": { + "symbol": "XPD", + "name": "Palladium Ounce", + "symbol_native": "XPD", + "decimal_digits": 6, + "rounding": 0, + "code": "XPD", + "name_plural": "Palladium Ounces", + "type": "metal", + "countries": [] + }, + "BTC": { + "symbol": "₿", + "name": "Bitcoin", + "symbol_native": "₿", + "decimal_digits": 8, + "rounding": 0, + "code": "BTC", + "name_plural": "Bitcoins", + "type": "crypto", + "countries": [] + }, + "ETH": { + "symbol": "Ξ", + "name": "Ethereum", + "symbol_native": "Ξ", + "decimal_digits": 18, + "rounding": 0, + "code": "ETH", + "name_plural": "Ethereum", + "type": "crypto", + "countries": [] + }, + "BNB": { + "symbol": "BNB", + "name": "Binance", + "symbol_native": "BNB", + "decimal_digits": 8, + "rounding": 0, + "code": "BNB", + "name_plural": "Binance", + "type": "crypto", + "countries": [] + }, + "XRP": { + "symbol": "XRP", + "name": "Ripple", + "symbol_native": "XRP", + "decimal_digits": 6, + "rounding": 0, + "code": "XRP", + "name_plural": "Ripple", + "type": "crypto", + "countries": [] + }, + "SOL": { + "symbol": "SOL", + "name": "Solana", + "symbol_native": "SOL", + "decimal_digits": 9, + "rounding": 0, + "code": "SOL", + "name_plural": "Solana", + "type": "crypto", + "countries": [] + }, + "DOT": { + "symbol": "DOT", + "name": "Polkadot", + "symbol_native": "DOT", + "decimal_digits": 10, + "rounding": 0, + "code": "DOT", + "name_plural": "Polkadot", + "type": "crypto", + "countries": [] + }, + "AVAX": { + "symbol": "AVAX", + "name": "Avalanche", + "symbol_native": "AVAX", + "decimal_digits": 18, + "rounding": 0, + "code": "AVAX", + "name_plural": "Avalanche", + "type": "crypto", + "countries": [] + }, + "MATIC": { + "symbol": "MATIC", + "name": "Matic Token", + "symbol_native": "MATIC", + "decimal_digits": 18, + "rounding": 0, + "code": "MATIC", + "name_plural": "Matic Tokens", + "type": "crypto", + "countries": [] + }, + "LTC": { + "symbol": "Ł", + "name": "Litecoin", + "symbol_native": "Ł", + "decimal_digits": 8, + "rounding": 0, + "code": "LTC", + "name_plural": "Litecoins", + "type": "crypto", + "countries": [] + }, + "ADA": { + "symbol": "ADA", + "name": "Cardano", + "symbol_native": "ADA", + "decimal_digits": 6, + "rounding": 0, + "code": "ADA", + "name_plural": "Cardanos", + "type": "crypto", + "countries": [] + }, + "USDT": { + "symbol": "USDT", + "name": "Tether", + "symbol_native": "USDT", + "decimal_digits": 2, + "rounding": 0, + "code": "USDT", + "name_plural": "Tethers", + "type": "crypto", + "countries": [] + }, + "USDC": { + "symbol": "USDC", + "name": "USD Coin", + "symbol_native": "USDC", + "decimal_digits": 2, + "rounding": 0, + "code": "USDC", + "name_plural": "USD Coins", + "type": "crypto", + "countries": [] + }, + "DAI": { + "symbol": "DAI", + "name": "Dai", + "symbol_native": "DAI", + "decimal_digits": 2, + "rounding": 0, + "code": "DAI", + "name_plural": "Dais", + "type": "crypto", + "countries": [] + }, + "ARB": { + "symbol": "ARB", + "name": "Arbitrum", + "symbol_native": "ARB", + "decimal_digits": 8, + "rounding": 0, + "code": "ARB", + "name_plural": "Arbitrums", + "type": "crypto", + "countries": [] + }, + "OP": { + "symbol": "OP", + "name": "Optimism", + "symbol_native": "OP", + "decimal_digits": 8, + "rounding": 0, + "code": "OP", + "name_plural": "Optimism", + "type": "crypto", + "countries": [] + }, + "VES": { + "symbol": "Bs.S.", + "name": "Venezuelan Bolívar", + "symbol_native": "Bs.S.", + "decimal_digits": 2, + "rounding": 0, + "code": "VES", + "name_plural": "Venezuelan bolívars", + "type": "fiat", + "countries": [] + }, + "STN": { + "symbol": "STN", + "name": "São Tomé and Príncipe dobra", + "symbol_native": "STN", + "decimal_digits": 2, + "rounding": 0, + "code": "STN", + "name_plural": "dobra", + "type": "fiat", + "countries": [] + }, + "MRU": { + "symbol": "MRU", + "name": "Mauritanian ouguiya", + "symbol_native": "MRU", + "decimal_digits": 2, + "rounding": 0, + "code": "MRU", + "name_plural": "ouguiya", + "type": "fiat", + "countries": [] + } + } +} diff --git a/src/infra/gateway/api.currencyapi-v3-latest.json b/src/infra/gateway/api.currencyapi-v3-latest.json new file mode 100644 index 000000000..e3279e17a --- /dev/null +++ b/src/infra/gateway/api.currencyapi-v3-latest.json @@ -0,0 +1,192 @@ +{ + "meta": { "last_updated_at": "2024-06-10T23:59:59Z" }, + "data": { + "ADA": { "code": "ADA", "value": 2.2679298487 }, + "AED": { "code": "AED", "value": 3.6718406559 }, + "AFN": { "code": "AFN", "value": 70.7904573332 }, + "ALL": { "code": "ALL", "value": 93.0320944204 }, + "AMD": { "code": "AMD", "value": 387.0652417589 }, + "ANG": { "code": "ANG", "value": 1.787280302 }, + "AOA": { "code": "AOA", "value": 851.5743892967 }, + "ARB": { "code": "ARB", "value": 1.0411768981 }, + "ARS": { "code": "ARS", "value": 901.9165772212 }, + "AUD": { "code": "AUD", "value": 1.5132802995 }, + "AVAX": { "code": "AVAX", "value": 0.0309532227 }, + "AWG": { "code": "AWG", "value": 1.79 }, + "AZN": { "code": "AZN", "value": 1.7 }, + "BAM": { "code": "BAM", "value": 1.8167003404 }, + "BBD": { "code": "BBD", "value": 2 }, + "BDT": { "code": "BDT", "value": 117.5903174764 }, + "BGN": { "code": "BGN", "value": 1.812830352 }, + "BHD": { "code": "BHD", "value": 0.376 }, + "BIF": { "code": "BIF", "value": 2874.4080899246 }, + "BMD": { "code": "BMD", "value": 1 }, + "BNB": { "code": "BNB", "value": 0.0015946085 }, + "BND": { "code": "BND", "value": 1.3515501742 }, + "BOB": { "code": "BOB", "value": 6.9369912102 }, + "BRL": { "code": "BRL", "value": 5.3570006385 }, + "BSD": { "code": "BSD", "value": 1 }, + "BTC": { "code": "BTC", "value": 1.43517e-5 }, + "BTN": { "code": "BTN", "value": 83.5026750681 }, + "BWP": { "code": "BWP", "value": 13.7488325074 }, + "BYN": { "code": "BYN", "value": 3.2699770637 }, + "BYR": { "code": "BYR", "value": 32699.762594218 }, + "BZD": { "code": "BZD", "value": 2 }, + "CAD": { "code": "CAD", "value": 1.3758702027 }, + "CDF": { "code": "CDF", "value": 2813.442771238 }, + "CHF": { "code": "CHF", "value": 0.8965300987 }, + "CLF": { "code": "CLF", "value": 0.0242000032 }, + "CLP": { "code": "CLP", "value": 921.0413972452 }, + "CNY": { "code": "CNY", "value": 7.2501309285 }, + "COP": { "code": "COP", "value": 3937.3301467823 }, + "CRC": { "code": "CRC", "value": 530.3078152726 }, + "CUC": { "code": "CUC", "value": 1 }, + "CUP": { "code": "CUP", "value": 24 }, + "CVE": { "code": "CVE", "value": 102.4397237684 }, + "CZK": { "code": "CZK", "value": 22.8602433711 }, + "DAI": { "code": "DAI", "value": 0.9992989749 }, + "DJF": { "code": "DJF", "value": 177.721 }, + "DKK": { "code": "DKK", "value": 6.9279413087 }, + "DOP": { "code": "DOP", "value": 59.4084267105 }, + "DOT": { "code": "DOT", "value": 0.1535316848 }, + "DZD": { "code": "DZD", "value": 134.9637578508 }, + "EGP": { "code": "EGP", "value": 47.718617933 }, + "ERN": { "code": "ERN", "value": 15 }, + "ETB": { "code": "ETB", "value": 56.9702884224 }, + "ETH": { "code": "ETH", "value": 0.0002724481 }, + "EUR": { "code": "EUR", "value": 0.9290801473 }, + "FJD": { "code": "FJD", "value": 2.2394803828 }, + "FKP": { "code": "FKP", "value": 0.7857969946 }, + "GBP": { "code": "GBP", "value": 0.7857400856 }, + "GEL": { "code": "GEL", "value": 2.8200704628 }, + "GGP": { "code": "GGP", "value": 0.7857970595 }, + "GHS": { "code": "GHS", "value": 14.9096017346 }, + "GIP": { "code": "GIP", "value": 0.7857973594 }, + "GMD": { "code": "GMD", "value": 58.2027859377 }, + "GNF": { "code": "GNF", "value": 8587.8742589067 }, + "GTQ": { "code": "GTQ", "value": 7.758281421 }, + "GYD": { "code": "GYD", "value": 209.1970978965 }, + "HKD": { "code": "HKD", "value": 7.8103013923 }, + "HNL": { "code": "HNL", "value": 24.7146626422 }, + "HRK": { "code": "HRK", "value": 6.6516508793 }, + "HTG": { "code": "HTG", "value": 134.7838194535 }, + "HUF": { "code": "HUF", "value": 364.8563849752 }, + "IDR": { "code": "IDR", "value": 16267.59254452 }, + "ILS": { "code": "ILS", "value": 3.7498404871 }, + "IMP": { "code": "IMP", "value": 0.78579737 }, + "INR": { "code": "INR", "value": 83.5191700278 }, + "IQD": { "code": "IQD", "value": 1308.6456434348 }, + "IRR": { "code": "IRR", "value": 42019.979875943 }, + "ISK": { "code": "ISK", "value": 139.2122370989 }, + "JEP": { "code": "JEP", "value": 0.7857974327 }, + "JMD": { "code": "JMD", "value": 155.2491448209 }, + "JOD": { "code": "JOD", "value": 0.71 }, + "JPY": { "code": "JPY", "value": 157.014696987 }, + "KES": { "code": "KES", "value": 129.3930234152 }, + "KGS": { "code": "KGS", "value": 86.7532661567 }, + "KHR": { "code": "KHR", "value": 4112.3295097742 }, + "KMF": { "code": "KMF", "value": 458.5398427806 }, + "KPW": { "code": "KPW", "value": 900.002639873 }, + "KRW": { "code": "KRW", "value": 1372.7226101559 }, + "KWD": { "code": "KWD", "value": 0.3068600384 }, + "KYD": { "code": "KYD", "value": 0.83333 }, + "KZT": { "code": "KZT", "value": 448.5436886523 }, + "LAK": { "code": "LAK", "value": 21719.745960039 }, + "LBP": { "code": "LBP", "value": 89589.77614652 }, + "LKR": { "code": "LKR", "value": 301.8763925636 }, + "LRD": { "code": "LRD", "value": 194.250797033 }, + "LSL": { "code": "LSL", "value": 18.740072297 }, + "LTC": { "code": "LTC", "value": 0.012540671 }, + "LTL": { "code": "LTL", "value": 3.2079216576 }, + "LVL": { "code": "LVL", "value": 0.6529558367 }, + "LYD": { "code": "LYD", "value": 4.8691905541 }, + "MAD": { "code": "MAD", "value": 9.9428813366 }, + "MATIC": { "code": "MATIC", "value": 1.5370217613 }, + "MDL": { "code": "MDL", "value": 17.6269922085 }, + "MGA": { "code": "MGA", "value": 4480.2747953954 }, + "MKD": { "code": "MKD", "value": 57.3124669066 }, + "MMK": { "code": "MMK", "value": 2095.624577851 }, + "MNT": { "code": "MNT", "value": 3399.6088715777 }, + "MOP": { "code": "MOP", "value": 8.0643411333 }, + "MRO": { "code": "MRO", "value": 356.999828 }, + "MRU": { "code": "MRU", "value": 39.5001473678 }, + "MUR": { "code": "MUR", "value": 46.5937585965 }, + "MVR": { "code": "MVR", "value": 15.4579725536 }, + "MWK": { "code": "MWK", "value": 1734.397497359 }, + "MXN": { "code": "MXN", "value": 18.55754282 }, + "MYR": { "code": "MYR", "value": 4.7216406039 }, + "MZN": { "code": "MZN", "value": 63.6050485402 }, + "NAD": { "code": "NAD", "value": 18.6629134402 }, + "NGN": { "code": "NGN", "value": 1465.9738916374 }, + "NIO": { "code": "NIO", "value": 36.7972487975 }, + "NOK": { "code": "NOK", "value": 10.6432812966 }, + "NPR": { "code": "NPR", "value": 133.0858524888 }, + "NZD": { "code": "NZD", "value": 1.6312602969 }, + "OMR": { "code": "OMR", "value": 0.3842500724 }, + "OP": { "code": "OP", "value": 0.4569196376 }, + "PAB": { "code": "PAB", "value": 0.9988701052 }, + "PEN": { "code": "PEN", "value": 3.7826704942 }, + "PGK": { "code": "PGK", "value": 3.8124903888 }, + "PHP": { "code": "PHP", "value": 58.7150182203 }, + "PKR": { "code": "PKR", "value": 278.478689251 }, + "PLN": { "code": "PLN", "value": 4.0171006006 }, + "PYG": { "code": "PYG", "value": 7551.7849655727 }, + "QAR": { "code": "QAR", "value": 3.6411907088 }, + "RON": { "code": "RON", "value": 4.6227706077 }, + "RSD": { "code": "RSD", "value": 108.3531882033 }, + "RUB": { "code": "RUB", "value": 88.8936112341 }, + "RWF": { "code": "RWF", "value": 1301.7783053399 }, + "SAR": { "code": "SAR", "value": 3.7456707423 }, + "SBD": { "code": "SBD", "value": 8.3580865701 }, + "SCR": { "code": "SCR", "value": 14.8189315961 }, + "SDG": { "code": "SDG", "value": 601.5 }, + "SEK": { "code": "SEK", "value": 10.476081969 }, + "SGD": { "code": "SGD", "value": 1.3524701839 }, + "SHP": { "code": "SHP", "value": 0.7857401163 }, + "SLL": { "code": "SLL", "value": 22424.082202439 }, + "SOL": { "code": "SOL", "value": 0.0062829578 }, + "SOS": { "code": "SOS", "value": 571.0236810379 }, + "SRD": { "code": "SRD", "value": 31.4496958362 }, + "STD": { "code": "STD", "value": 22799.367791132 }, + "STN": { "code": "STN", "value": 22.7993551837 }, + "SVC": { "code": "SVC", "value": 8.75 }, + "SYP": { "code": "SYP", "value": 12994.621714013 }, + "SZL": { "code": "SZL", "value": 18.6989824867 }, + "THB": { "code": "THB", "value": 36.7206552011 }, + "TJS": { "code": "TJS", "value": 10.7309711957 }, + "TMT": { "code": "TMT", "value": 3.5 }, + "TND": { "code": "TND", "value": 3.1210104046 }, + "TOP": { "code": "TOP", "value": 2.341250381 }, + "TRY": { "code": "TRY", "value": 32.3123541442 }, + "TTD": { "code": "TTD", "value": 6.7413709853 }, + "TWD": { "code": "TWD", "value": 32.4375140234 }, + "TZS": { "code": "TZS", "value": 2593.4598939473 }, + "UAH": { "code": "UAH", "value": 40.3861148848 }, + "UGX": { "code": "UGX", "value": 3793.8622299526 }, + "USD": { "code": "USD", "value": 1 }, + "USDC": { "code": "USDC", "value": 0.9995108127 }, + "USDT": { "code": "USDT", "value": 0.9993678097 }, + "UYU": { "code": "UYU", "value": 38.8978562435 }, + "UZS": { "code": "UZS", "value": 12668.129512123 }, + "VEF": { "code": "VEF", "value": 3640407.7001116 }, + "VES": { "code": "VES", "value": 36.4040600039 }, + "VND": { "code": "VND", "value": 25421.433686036 }, + "VUV": { "code": "VUV", "value": 119.9011334188 }, + "WST": { "code": "WST", "value": 2.7389439335 }, + "XAF": { "code": "XAF", "value": 609.3574609173 }, + "XAG": { "code": "XAG", "value": 0.033659259 }, + "XAU": { "code": "XAU", "value": 0.0004329255 }, + "XCD": { "code": "XCD", "value": 2.7 }, + "XDR": { "code": "XDR", "value": 0.7558400986 }, + "XOF": { "code": "XOF", "value": 609.3574713759 }, + "XPD": { "code": "XPD", "value": 0.0011018596 }, + "XPF": { "code": "XPF", "value": 110.7528319197 }, + "XPT": { "code": "XPT", "value": 0.001027921 }, + "XRP": { "code": "XRP", "value": 2.0094059521 }, + "YER": { "code": "YER", "value": 249.9532931212 }, + "ZAR": { "code": "ZAR", "value": 18.7007830651 }, + "ZMK": { "code": "ZMK", "value": 9001.2 }, + "ZMW": { "code": "ZMW", "value": 26.4433644143 }, + "ZWL": { "code": "ZWL", "value": 13.5412641357 } + } +} diff --git a/src/infra/gateway/currency-api-gateway-online.ts b/src/infra/gateway/currency-api-gateway-online.ts new file mode 100644 index 000000000..6830f2885 --- /dev/null +++ b/src/infra/gateway/currency-api-gateway-online.ts @@ -0,0 +1,29 @@ +import { CurrenciesResponse, CurrencyApiGateway, Filters, RateResponse } from "@/infra/gateway/currency-api-gateway"; +import HttpClient from "@/infra/http-client/http-client"; + +export class CurrenciesApiGatewayOnline implements CurrencyApiGateway { + constructor(private readonly httpClient: HttpClient) { + httpClient.setSettings({ + baseURL: 'https://api.currencyapi.com/v3/', + headers: { + apiKey: 'cur_live_cuO1rc3J9p5XJE9QYRgyadj3ERuP57GKFUdt9yOh' + } + }) + } + + getCurrencies(filters?: Filters): Promise { + const params: { [key: string]: any } = { + ...(filters?.currencies && { currencies: filters.currencies }), + ...(filters?.type && { type: filters.type }) + }; + return this.httpClient.get('/currencies', { params }); + } + + getRates(filters?: Filters): Promise { + const params: { [key: string]: any } = { + ...(filters?.currencies && { currencies: filters.currencies }), + ...(filters?.type && { type: filters.type }) + }; + return this.httpClient.get('/latest', { params }) + } +} \ No newline at end of file diff --git a/src/infra/gateway/currency-api-gateway-static.ts b/src/infra/gateway/currency-api-gateway-static.ts new file mode 100644 index 000000000..2a508699f --- /dev/null +++ b/src/infra/gateway/currency-api-gateway-static.ts @@ -0,0 +1,68 @@ +import { CurrenciesResponse, CurrencyApiGateway, Filters, RateResponse } from "@/infra/gateway/currency-api-gateway"; +import { readFileSync } from 'fs'; +import path from 'path'; + +export class CurrenciesApiGatewayStatic implements CurrencyApiGateway { + private currenciesMap: Map; + private ratesMap: Map; + private lastUpdatedAt!: Date; + + constructor() { + this.currenciesMap = new Map(); + this.ratesMap = new Map(); + this.loadData(); + } + + private async loadData(): Promise { + const currenciesFilePath = path.resolve(__dirname, './api.currencyapi-v3-currencies.json'); + const ratesFilePath = path.resolve(__dirname, './api.currencyapi-v3-latest.json'); + + const currenciesData = readFileSync(currenciesFilePath, 'utf-8'); + const ratesData = readFileSync(ratesFilePath, 'utf-8'); + + const currenciesResponse: CurrenciesResponse = JSON.parse(currenciesData); + const ratesResponse: RateResponse = JSON.parse(ratesData); + + this.lastUpdatedAt = ratesResponse.meta.last_updated_at; + + for (const [code, currency] of Object.entries(currenciesResponse.data)) { + this.currenciesMap.set(code, currency); + } + + for (const [code, rate] of Object.entries(ratesResponse.data)) { + this.ratesMap.set(code, rate); + } + } + + async getCurrencies(filters?: Filters): Promise { + const filteredCurrencies = Array.from(this.currenciesMap.entries()).reduce((acc, [code, currency]) => { + if ( + (!filters?.currencies || filters.currencies.includes(code)) && + (!filters?.type || currency.type === filters.type) + ) { + acc[code] = currency; + } + return acc; + }, {} as { [key: string]: any }); + + return { data: filteredCurrencies }; + } + + async getRates(filters?: Filters): Promise { + const filteredRates = Array.from(this.ratesMap.entries()).reduce((acc, [code, rate]) => { + if ( + (!filters?.currencies || filters.currencies.includes(code)) && + (!filters?.type || this.currenciesMap.get(code)?.type === filters.type) + ) { + acc[code] = rate; + } + return acc; + }, {} as { [key: string]: any }); + + + return { + meta: { last_updated_at: this.lastUpdatedAt }, + data: filteredRates + }; + } +} diff --git a/src/infra/gateway/currency-api-gateway.ts b/src/infra/gateway/currency-api-gateway.ts new file mode 100644 index 000000000..99761a3be --- /dev/null +++ b/src/infra/gateway/currency-api-gateway.ts @@ -0,0 +1,38 @@ +export interface CurrencyApiGateway { + getCurrencies(filters?: Filters): Promise; + getRates(filters?: Filters): Promise; +} + +export type Filters = { currencies?: string[], type?: CurrencyType } + +export type CurrencyType = 'fiat' | 'metal' | 'crypto' + +export type CurrenciesResponse = { + data: { + [key: string]: { + symbol: string + name: string + symbol_native: string + decimal_digits: number + rounding: number + code: string + name_plural: string + type: CurrencyType + countries: string[] + } + } +} + +export type RateResponse = { + meta: { + last_updated_at: Date + } + data: { + [key: string]: { + code: string + value: number + } + } +} + +export default CurrencyApiGateway; \ No newline at end of file diff --git a/src/infra/http-client/axios-adapter.ts b/src/infra/http-client/axios-adapter.ts new file mode 100644 index 000000000..2c7e5a544 --- /dev/null +++ b/src/infra/http-client/axios-adapter.ts @@ -0,0 +1,29 @@ +import HttpClient, { Options, Settings } from "@/infra/http-client/http-client" +import axios from "axios" + +export class AxiosAdapter extends HttpClient { + api: axios.AxiosInstance + + constructor(settings: Settings = { baseURL: '', headers: {} }) { + super(settings) + this.api = axios.create({ + baseURL: settings.baseURL, + headers: settings.headers + }) + } + + async get(url: string, options?: Options): Promise { + const response = await this.api.get(url, options) + return response.data + } + + setSettings(settings: { [k in keyof Settings]?: Settings[k] }): void { + if (settings.baseURL) this.api.defaults.baseURL = settings.baseURL + if (settings.headers) { + for (const key in settings.headers) { + this.api.defaults.headers[key] = settings.headers[key] + } + } + } + +} \ No newline at end of file diff --git a/src/infra/http-client/http-client.ts b/src/infra/http-client/http-client.ts new file mode 100644 index 000000000..b004c5411 --- /dev/null +++ b/src/infra/http-client/http-client.ts @@ -0,0 +1,19 @@ +export abstract class HttpClient { + constructor(protected readonly settings: Settings) { + this.setSettings(settings) + } + abstract setSettings(settings: { [k in keyof Settings]?: Settings[k] }): void + abstract get(url: string, options?: Options): Promise +} + +export type Settings = { + baseURL: string + headers: { [key: string]: string } +} + +export type Options = { + params?: { [key: string]: any } + headers?: { [key: string]: string } +} + +export default HttpClient \ No newline at end of file diff --git a/src/infra/http-server/currency.controller.ts b/src/infra/http-server/currency.controller.ts index 19bc99acf..5e53d99b4 100644 --- a/src/infra/http-server/currency.controller.ts +++ b/src/infra/http-server/currency.controller.ts @@ -12,24 +12,42 @@ export class CurrencyController { readonly getCurrencyRateByCode: GetCurrencyRateByCode, readonly deleteCurrencyRateByCode: DeleteCurrencyRateByCode ) { + httpServer.register('get', '/currencies/convert', async ({ query }) => { - const { from, to, amount } = query; - const input = { from, to, amount }; + const input = { + from: query.from.toUpperCase(), + to: query.to.toUpperCase(), + amount: Number(query.amount) + }; const output = await this.convertCurrency.execute(input); - return output + return { + from: input.from, + to: input.to, + givenAmount: input.amount, + convertedAmount: output.value, + decimalPoints: output.decimalPoints, + symbol: output.symbol, + convertedAmountFormatted: `${output.symbol} ${output.value.toFixed(output.decimalPoints)}`, + } }) + httpServer.register('post', '/currencies', async ({ body }) => { - const input: CreateCurrencyRateDto = { - name: body.name, - code: body.code, - symbol: body.symbol, - decimalPoints: body.decimalPoints, - baseAmount: body.baseAmount, - equivalentCurrencyAmount: body.equivalentCurrencyAmount, - equivalentCurrencyCode: body.equivalentCurrencyCode + try { + const input: CreateCurrencyRateDto = { + name: body.name, + code: body.code, + symbol: body.symbol, + decimalPoints: body.decimalPoints, + baseAmount: body.baseAmount, + equivalentCurrencyAmount: body.equivalentCurrencyAmount, + equivalentCurrencyCode: body.equivalentCurrencyCode + } + const output = await this.createCurrencyRate.execute(input); + return output + } catch (error: any) { + console.error(error.message) + throw error } - const output = await this.createCurrencyRate.execute(input); - return output }) httpServer.register('get', '/currencies/{code}', async ({ params }) => { const { code } = params; diff --git a/src/infra/repository/currency-rate-repository-api.ts b/src/infra/repository/currency-rate-repository-api.ts new file mode 100644 index 000000000..519604421 --- /dev/null +++ b/src/infra/repository/currency-rate-repository-api.ts @@ -0,0 +1,49 @@ +import CurrencyRateRepository from "@/application/repository/currency-rate-repository"; +import CurrencyRate from "@/domain/currency-rate"; +import CurrencyRateNotFoundError from "@/domain/errors/currency-rate-not-found.error"; +import CurrencyApiGateway from "../gateway/currency-api-gateway"; + +export class CurrencyRateRepositoryApi implements CurrencyRateRepository { + currencyMap: Map; + + constructor(private readonly currencyGateway: CurrencyApiGateway) { + this.currencyMap = new Map(); + } + + async findByCode(code: string): Promise { + if (this.currencyMap.has(code)) { + return this.currencyMap.get(code)!; + } + const currencyResponse = await this.currencyGateway.getCurrencies({ currencies: [code] }); + const currencyData = currencyResponse.data[code]; + if (!currencyResponse.data[code] || !currencyResponse.data[code]) { + throw new CurrencyRateNotFoundError(code); + } + const rateResponse = await this.currencyGateway.getRates({ currencies: [code] }); + const rateData = rateResponse.data[code]; + const currency = new CurrencyRate( + currencyData.code, + currencyData.name, + currencyData.code, + currencyData.symbol, + currencyData.decimal_digits, + rateData.value + ); + return currency; + } + + async add(currencyRate: CurrencyRate): Promise { + if (this.currencyMap.has(currencyRate.code)) { + throw new Error('Currency already exists'); + } + this.currencyMap.set(currencyRate.code, currencyRate); + } + + async delete(code: string): Promise { + if (!this.currencyMap.has(code)) { + throw new Error('Currency not found'); + } + this.currencyMap.delete(code); + } + +} \ No newline at end of file diff --git a/src/main/main-gateway.ts b/src/main/main-gateway.ts new file mode 100644 index 000000000..1e1db9257 --- /dev/null +++ b/src/main/main-gateway.ts @@ -0,0 +1,21 @@ +import ConvertCurrency from "@/application/use-case/convert-currency"; +import CreateCurrencyRate from "@/application/use-case/create-currency-rate"; +import { DeleteCurrencyRateByCode } from "@/application/use-case/delete-currency-rate-by-code"; +import GetCurrencyRateByCode from "@/application/use-case/get-currency-rate-by-code"; +import { CurrenciesApiGatewayOnline } from "@/infra/gateway/currency-api-gateway-online"; +import { AxiosAdapter } from "@/infra/http-client/axios-adapter"; +import { CurrencyController } from "@/infra/http-server/currency.controller"; +import { ExpressAdapter } from "@/infra/http-server/express-adapter"; +import { CurrencyRateRepositoryApi } from "@/infra/repository/currency-rate-repository-api"; + +const httpServer = new ExpressAdapter(); +// const currencyGateway = new CurrenciesApiGatewayStatic() +const httpClient = new AxiosAdapter(); +const currencyGateway = new CurrenciesApiGatewayOnline(httpClient); +const currencyRateRepository = new CurrencyRateRepositoryApi(currencyGateway); +const currencyConverter = new ConvertCurrency(currencyRateRepository) +const createCurrencyRate = new CreateCurrencyRate(currencyRateRepository) +const getCurrencyRateByCode = new GetCurrencyRateByCode(currencyRateRepository) +const deleteCurrencyRateByCode = new DeleteCurrencyRateByCode(currencyRateRepository) +new CurrencyController(httpServer, currencyConverter, createCurrencyRate, getCurrencyRateByCode, deleteCurrencyRateByCode) +httpServer.listen(3000); \ No newline at end of file diff --git a/test/integration/api.test.ts b/test/integration/api.test.ts index 160696058..f33c1fe82 100644 --- a/test/integration/api.test.ts +++ b/test/integration/api.test.ts @@ -1,34 +1,48 @@ import axios from 'axios'; -describe('Main Express', () => { +describe('Main API', () => { it('should convert USD to BRL', async () => { const params = { from: 'USD', to: 'BRL', amount: 10 }; const response = await axios.get('http://localhost:3000/currencies/convert', { params }); const output = response.data; - expect(output).toEqual({ value: 52, symbol: 'R$', decimalPoints: 2 }); + expect(output.from).toBe('USD'); + expect(output.to).toBe('BRL'); + expect(output.givenAmount).toBe(10); + expect(output.convertedAmount).toBeGreaterThan(0); + expect(output.convertedAmountFormatted).toContain('R$'); + expect(output.decimalPoints).toBe(2); + expect(output.symbol).toBe('R$'); }); it('should convert BRL to USD', async () => { - const params = { from: 'BRL', to: 'USD', amount: 10.4 }; + const brlResponse = await axios.get('http://localhost:3000/currencies/BRL'); + const brlOutput = brlResponse.data; + const amount = brlOutput.rate * 2 + const params = { from: 'brl', to: 'usd', amount: amount }; const response = await axios.get('http://localhost:3000/currencies/convert', { params }); const output = response.data; - expect(output).toEqual({ value: 2, symbol: '$', decimalPoints: 2 }); + expect(output.from).toBe('BRL'); + expect(output.to).toBe('USD'); + expect(output.givenAmount).toBe(amount); + expect(output.convertedAmount).toBe(2.00); + expect(output.convertedAmountFormatted).toContain('$'); + expect(output.decimalPoints).toBe(2); + expect(output.symbol).toBe('$'); }); it('should create, get and delete a currency rate', async () => { + const payload = { name: 'Any Currency', - code: 'ANY', + code: 'ANYC', symbol: '$', decimalPoints: 2, baseAmount: 2, equivalentCurrencyAmount: 1, equivalentCurrencyCode: 'USD' }; - const createResponse = await axios.post('http://localhost:3000/currencies', payload); - const createOutput = createResponse.data; - expect(createOutput.currencyRateId).toBeDefined(); + await axios.post('http://localhost:3000/currencies', payload); const getResponse = await axios.get(`http://localhost:3000/currencies/${payload.code}`); const getOutput = getResponse.data; expect(getOutput.name).toEqual(payload.name); @@ -39,6 +53,7 @@ describe('Main Express', () => { const deleteResponse = await axios.delete(`http://localhost:3000/currencies/${getOutput.code}`); const deleteOutput = deleteResponse.data; expect(deleteOutput).toEqual({ message: 'Currency deleted' }); + }); it('should throw an error when trying to convert with invalid currency', async () => { From ad05b36739f8748c8eefb514c1e165ea2fdd6778 Mon Sep 17 00:00:00 2001 From: Leandro Reis Date: Thu, 13 Jun 2024 11:54:24 -0300 Subject: [PATCH 24/43] feat: restructured imports and exports, added index.ts files to re-export internal modules - Added `index.ts` files in various module directories to simplify imports and exports. - Updated all files to use the new restructured import paths. - Organized project structure to improve code clarity and maintainability. --- .../repository/currency-rate-repository.ts | 6 ++++-- src/application/repository/index.ts | 1 + .../service/equivalent-rate-calculator.ts | 2 +- src/application/service/index.ts | 1 + src/application/use-case/convert-currency.ts | 14 +++++++------ .../use-case/create-currency-rate.ts | 20 ++++++++++--------- .../use-case/delete-currency-rate-by-code.ts | 6 ++++-- .../use-case/get-currency-rate-by-code.ts | 6 +++--- src/application/use-case/index.ts | 4 ++++ src/domain/{ => entity}/currency-rate.ts | 12 +++++------ src/domain/entity/index.ts | 1 + .../errors/currency-rate-not-found.error.ts | 6 ++++-- src/domain/errors/index.ts | 4 ++++ src/domain/errors/invalid-param.error.ts | 6 ++++-- src/domain/errors/not-found.error.ts | 6 ++++-- src/domain/value-object/index.ts | 6 ++++++ src/infra/database/database-connection.ts | 4 +++- src/infra/database/index.ts | 2 ++ src/infra/database/pgpromise-adapter.ts | 6 ++++-- .../gateway/currency-api-gateway-online.ts | 4 ++-- .../gateway/currency-api-gateway-static.ts | 2 +- src/infra/gateway/index.ts | 3 +++ src/infra/http-client/axios-adapter.ts | 6 ++++-- src/infra/http-client/index.ts | 2 ++ src/infra/http-server/currency.controller.ts | 16 +++++++++------ src/infra/http-server/express-adapter.ts | 2 +- src/infra/http-server/hapi-adapter.ts | 2 +- src/infra/http-server/index.ts | 5 +++++ .../currency-rate-repository-api.ts | 12 ++++++----- .../currency-rate-repository-database.ts | 12 ++++++----- .../currency-rate-repository-fake.ts | 12 ++++++----- src/infra/repository/index.ts | 3 +++ src/main/main-express-pg.ts | 17 ++++++++-------- src/main/main-express.ts | 15 +++++++------- src/main/main-gateway.ts | 19 +++++++++--------- src/main/main-hapi.ts | 15 +++++++------- test/unity/convert-currency.test.ts | 4 ++-- test/unity/create-currency-rate.test.ts | 7 +++---- test/unity/currenty-rate.test.ts | 2 +- test/unity/delete-currency-rate.test.ts | 6 ++---- test/unity/equivalent-rate-calculator.test.ts | 2 +- 41 files changed, 171 insertions(+), 110 deletions(-) create mode 100644 src/application/repository/index.ts create mode 100644 src/application/service/index.ts create mode 100644 src/application/use-case/index.ts rename src/domain/{ => entity}/currency-rate.ts (82%) create mode 100644 src/domain/entity/index.ts create mode 100644 src/domain/errors/index.ts create mode 100644 src/domain/value-object/index.ts create mode 100644 src/infra/database/index.ts create mode 100644 src/infra/gateway/index.ts create mode 100644 src/infra/http-client/index.ts create mode 100644 src/infra/http-server/index.ts create mode 100644 src/infra/repository/index.ts diff --git a/src/application/repository/currency-rate-repository.ts b/src/application/repository/currency-rate-repository.ts index 150757a31..3d0f6c20c 100644 --- a/src/application/repository/currency-rate-repository.ts +++ b/src/application/repository/currency-rate-repository.ts @@ -1,7 +1,9 @@ -import CurrencyRate from "@/domain/currency-rate"; +import { CurrencyRate } from "@/domain/entity/"; -export default interface CurrencyRateRepository { +export interface CurrencyRateRepository { findByCode(code: string): Promise; add(currencyRate: CurrencyRate): Promise; delete(code: string): Promise; } + +export default CurrencyRateRepository; \ No newline at end of file diff --git a/src/application/repository/index.ts b/src/application/repository/index.ts new file mode 100644 index 000000000..a7be7add5 --- /dev/null +++ b/src/application/repository/index.ts @@ -0,0 +1 @@ +export * from './currency-rate-repository'; diff --git a/src/application/service/equivalent-rate-calculator.ts b/src/application/service/equivalent-rate-calculator.ts index 79ba3b145..d158e7b90 100644 --- a/src/application/service/equivalent-rate-calculator.ts +++ b/src/application/service/equivalent-rate-calculator.ts @@ -1,4 +1,4 @@ -import InvalidParamError from "@/domain/errors/invalid-param.error"; +import { InvalidParamError } from "@/domain/errors"; export class EquivalentRateCalculator { diff --git a/src/application/service/index.ts b/src/application/service/index.ts new file mode 100644 index 000000000..0002d585b --- /dev/null +++ b/src/application/service/index.ts @@ -0,0 +1 @@ +export * from './equivalent-rate-calculator'; diff --git a/src/application/use-case/convert-currency.ts b/src/application/use-case/convert-currency.ts index ca10ff18e..dce5173dc 100644 --- a/src/application/use-case/convert-currency.ts +++ b/src/application/use-case/convert-currency.ts @@ -1,10 +1,10 @@ -import CurrencyRateRepository from "../repository/currency-rate-repository"; +import { CurrencyRateRepository } from "@/application/repository"; -export default class ConvertCurrency { +export class ConvertCurrency { constructor(readonly currencyRateRepository: CurrencyRateRepository) { } - async execute(input: Input): Promise { + async execute(input: ConvertCurrencyDto): Promise { const exchangeFrom = await this.currencyRateRepository.findByCode(input.from); const exchangeTo = await this.currencyRateRepository.findByCode(input.to); const convertedAmount = exchangeFrom.convertTo(exchangeTo, input.amount); @@ -16,14 +16,16 @@ export default class ConvertCurrency { } } -type Input = { +export type ConvertCurrencyDto = { from: string; to: string; amount: number; } -type Output = { +export type ConvertCurrencyResponse = { value: number; symbol: string; decimalPoints: number; -} \ No newline at end of file +} + +export default ConvertCurrency; \ No newline at end of file diff --git a/src/application/use-case/create-currency-rate.ts b/src/application/use-case/create-currency-rate.ts index 3151fe9cb..513e3c968 100644 --- a/src/application/use-case/create-currency-rate.ts +++ b/src/application/use-case/create-currency-rate.ts @@ -1,15 +1,15 @@ -import CurrencyRateRepository from "@/application/repository/currency-rate-repository"; -import EquivalentRateCalculator from "@/application/service/equivalent-rate-calculator"; -import CurrencyRate from "@/domain/currency-rate"; +import { CurrencyRateRepository } from "@/application/repository"; +import { EquivalentRateCalculator } from "@/application/service"; +import { CurrencyRate } from "@/domain/entity/"; -export default class CreateCurrencyRate { +export class CreateCurrencyRate { constructor(private currencyRateRepository: CurrencyRateRepository) { } - async execute(data: CreateCurrencyRateDto): Promise { - const equivalentCurrencyRate = await this.currencyRateRepository.findByCode(data.equivalentCurrencyCode.toUpperCase()); + async execute(input: CreateCurrencyRateDto): Promise { + const equivalentCurrencyRate = await this.currencyRateRepository.findByCode(input.equivalentCurrencyCode.toUpperCase()); const equivalentRateCalculator = new EquivalentRateCalculator(equivalentCurrencyRate.rate); - const rate = equivalentRateCalculator.calculateRate(data.baseAmount, data.equivalentCurrencyAmount) - const currencyRate = CurrencyRate.create(data.name, data.code, data.symbol, data.decimalPoints, rate); + const rate = equivalentRateCalculator.calculateRate(input.baseAmount, input.equivalentCurrencyAmount) + const currencyRate = CurrencyRate.create(input.name, input.code, input.symbol, input.decimalPoints, rate); await this.currencyRateRepository.add(currencyRate); return { currencyRateId: currencyRate.id }; } @@ -25,6 +25,8 @@ export type CreateCurrencyRateDto = { equivalentCurrencyAmount: number; } -export type Output = { +export type CreateCurrencyRateOutput = { currencyRateId: string; } + +export default CreateCurrencyRate; \ No newline at end of file diff --git a/src/application/use-case/delete-currency-rate-by-code.ts b/src/application/use-case/delete-currency-rate-by-code.ts index 1be03eade..c285d53c6 100644 --- a/src/application/use-case/delete-currency-rate-by-code.ts +++ b/src/application/use-case/delete-currency-rate-by-code.ts @@ -1,4 +1,4 @@ -import CurrencyRateRepository from "../repository/currency-rate-repository"; +import { CurrencyRateRepository } from "@/application/repository"; export class DeleteCurrencyRateByCode { constructor(private readonly currencyRateRepository: CurrencyRateRepository) { } @@ -13,4 +13,6 @@ export class DeleteCurrencyRateByCode { } await this.currencyRateRepository.delete(currencyRate.code); } -} \ No newline at end of file +} + +export default DeleteCurrencyRateByCode; \ No newline at end of file diff --git a/src/application/use-case/get-currency-rate-by-code.ts b/src/application/use-case/get-currency-rate-by-code.ts index aeb21ecc9..175050192 100644 --- a/src/application/use-case/get-currency-rate-by-code.ts +++ b/src/application/use-case/get-currency-rate-by-code.ts @@ -1,10 +1,10 @@ -import CurrencyRateRepository from "@/application/repository/currency-rate-repository"; +import { CurrencyRateRepository } from "@/application/repository"; export class GetCurrencyRateByCode { constructor(readonly currencyRateRepository: CurrencyRateRepository) { } - async execute(code: string): Promise { + async execute(code: string): Promise { const currencyRate = await this.currencyRateRepository.findByCode(code); return { id: currencyRate.id, @@ -18,7 +18,7 @@ export class GetCurrencyRateByCode { } -export type Output = { +export type GetCurrencyRateByCodeOutput = { id: string; name: string; code: string; diff --git a/src/application/use-case/index.ts b/src/application/use-case/index.ts new file mode 100644 index 000000000..defdfbe56 --- /dev/null +++ b/src/application/use-case/index.ts @@ -0,0 +1,4 @@ +export * from './convert-currency'; +export * from './create-currency-rate'; +export * from './delete-currency-rate-by-code'; +export * from './get-currency-rate-by-code'; diff --git a/src/domain/currency-rate.ts b/src/domain/entity/currency-rate.ts similarity index 82% rename from src/domain/currency-rate.ts rename to src/domain/entity/currency-rate.ts index eff0bb342..83261f15a 100644 --- a/src/domain/currency-rate.ts +++ b/src/domain/entity/currency-rate.ts @@ -1,12 +1,8 @@ import currency from "currency.js"; -import Code from "@/domain/value-object/code"; -import DecimalPoints from "@/domain/value-object/decimal-points"; -import Name from "@/domain/value-object/name"; -import Rate from "@/domain/value-object/rate"; -import Symbol from "@/domain/value-object/symbol"; +import { Code, DecimalPoints, Name, Rate, Symbol } from "@/domain/value-object"; -export default class CurrencyRate { +export class CurrencyRate { private _name: Name; private _code: Code; private _symbol: Symbol; @@ -62,4 +58,6 @@ export default class CurrencyRate { return amountCurrency.value; } -} \ No newline at end of file +} + +export default CurrencyRate; \ No newline at end of file diff --git a/src/domain/entity/index.ts b/src/domain/entity/index.ts new file mode 100644 index 000000000..8d2059e7a --- /dev/null +++ b/src/domain/entity/index.ts @@ -0,0 +1 @@ +export * from './currency-rate'; diff --git a/src/domain/errors/currency-rate-not-found.error.ts b/src/domain/errors/currency-rate-not-found.error.ts index ae7b5a9d4..000e6c366 100644 --- a/src/domain/errors/currency-rate-not-found.error.ts +++ b/src/domain/errors/currency-rate-not-found.error.ts @@ -1,8 +1,10 @@ import NotFoundError from "./not-found.error"; -export default class CurrencyRateNotFoundError extends NotFoundError { +export class CurrencyRateNotFoundError extends NotFoundError { constructor(currencyRateIdOrCode: string) { super(`Currency rate not found: ${currencyRateIdOrCode}`); this.name = 'CurrencyRateNotFoundError'; } -} \ No newline at end of file +} + +export default CurrencyRateNotFoundError; \ No newline at end of file diff --git a/src/domain/errors/index.ts b/src/domain/errors/index.ts new file mode 100644 index 000000000..9081b991c --- /dev/null +++ b/src/domain/errors/index.ts @@ -0,0 +1,4 @@ +export * from './currency-rate-not-found.error'; +export * from './invalid-param.error'; +export * from './not-found.error'; + diff --git a/src/domain/errors/invalid-param.error.ts b/src/domain/errors/invalid-param.error.ts index e9f2d7bf4..c22dae97d 100644 --- a/src/domain/errors/invalid-param.error.ts +++ b/src/domain/errors/invalid-param.error.ts @@ -1,6 +1,8 @@ -export default class InvalidParamError extends Error { +export class InvalidParamError extends Error { constructor(message: string) { super(message); this.name = 'InvalidParamsError'; } -} \ No newline at end of file +} + +export default InvalidParamError; \ No newline at end of file diff --git a/src/domain/errors/not-found.error.ts b/src/domain/errors/not-found.error.ts index 3b82a7e9a..1920bcf7f 100644 --- a/src/domain/errors/not-found.error.ts +++ b/src/domain/errors/not-found.error.ts @@ -1,6 +1,8 @@ -export default class NotFoundError extends Error { +export class NotFoundError extends Error { constructor(message: string) { super(message); this.name = 'NotFoundError'; } -} \ No newline at end of file +} + +export default NotFoundError; \ No newline at end of file diff --git a/src/domain/value-object/index.ts b/src/domain/value-object/index.ts new file mode 100644 index 000000000..a7c1197ff --- /dev/null +++ b/src/domain/value-object/index.ts @@ -0,0 +1,6 @@ +export * from './code'; +export * from './decimal-points'; +export * from './name'; +export * from './rate'; +export * from './symbol'; + diff --git a/src/infra/database/database-connection.ts b/src/infra/database/database-connection.ts index 503d1ab2c..e92374a48 100644 --- a/src/infra/database/database-connection.ts +++ b/src/infra/database/database-connection.ts @@ -1,4 +1,6 @@ export interface DatabaseConnection { query(statement: string, params: TParams): Promise close(): Promise -} \ No newline at end of file +} + +export default DatabaseConnection; \ No newline at end of file diff --git a/src/infra/database/index.ts b/src/infra/database/index.ts new file mode 100644 index 000000000..07833bbea --- /dev/null +++ b/src/infra/database/index.ts @@ -0,0 +1,2 @@ +export * from './database-connection'; +export * from './pgpromise-adapter'; diff --git a/src/infra/database/pgpromise-adapter.ts b/src/infra/database/pgpromise-adapter.ts index 9243d2d88..4c077461e 100644 --- a/src/infra/database/pgpromise-adapter.ts +++ b/src/infra/database/pgpromise-adapter.ts @@ -1,4 +1,4 @@ -import { DatabaseConnection } from "./database-connection"; +import { DatabaseConnection } from "@/infra/database"; import pgp from "pg-promise"; @@ -17,4 +17,6 @@ export class PgPromiseAdapter implements DatabaseConnection { return this.connection.$pool.end(); } -} \ No newline at end of file +} + +export default PgPromiseAdapter; \ No newline at end of file diff --git a/src/infra/gateway/currency-api-gateway-online.ts b/src/infra/gateway/currency-api-gateway-online.ts index 6830f2885..1d3089634 100644 --- a/src/infra/gateway/currency-api-gateway-online.ts +++ b/src/infra/gateway/currency-api-gateway-online.ts @@ -1,5 +1,5 @@ -import { CurrenciesResponse, CurrencyApiGateway, Filters, RateResponse } from "@/infra/gateway/currency-api-gateway"; -import HttpClient from "@/infra/http-client/http-client"; +import { CurrenciesResponse, CurrencyApiGateway, Filters, RateResponse } from "@/infra/gateway/"; +import { HttpClient } from "@/infra/http-client"; export class CurrenciesApiGatewayOnline implements CurrencyApiGateway { constructor(private readonly httpClient: HttpClient) { diff --git a/src/infra/gateway/currency-api-gateway-static.ts b/src/infra/gateway/currency-api-gateway-static.ts index 2a508699f..5b37d2089 100644 --- a/src/infra/gateway/currency-api-gateway-static.ts +++ b/src/infra/gateway/currency-api-gateway-static.ts @@ -1,4 +1,4 @@ -import { CurrenciesResponse, CurrencyApiGateway, Filters, RateResponse } from "@/infra/gateway/currency-api-gateway"; +import { CurrenciesResponse, CurrencyApiGateway, Filters, RateResponse } from "@/infra/gateway/"; import { readFileSync } from 'fs'; import path from 'path'; diff --git a/src/infra/gateway/index.ts b/src/infra/gateway/index.ts new file mode 100644 index 000000000..769c4db92 --- /dev/null +++ b/src/infra/gateway/index.ts @@ -0,0 +1,3 @@ +export * from './currency-api-gateway'; +export * from './currency-api-gateway-online'; +export * from './currency-api-gateway-static'; diff --git a/src/infra/http-client/axios-adapter.ts b/src/infra/http-client/axios-adapter.ts index 2c7e5a544..f2f42065a 100644 --- a/src/infra/http-client/axios-adapter.ts +++ b/src/infra/http-client/axios-adapter.ts @@ -1,4 +1,4 @@ -import HttpClient, { Options, Settings } from "@/infra/http-client/http-client" +import { HttpClient, Options, Settings } from "@/infra/http-client" import axios from "axios" export class AxiosAdapter extends HttpClient { @@ -26,4 +26,6 @@ export class AxiosAdapter extends HttpClient { } } -} \ No newline at end of file +} + +export default AxiosAdapter; \ No newline at end of file diff --git a/src/infra/http-client/index.ts b/src/infra/http-client/index.ts new file mode 100644 index 000000000..51863ebef --- /dev/null +++ b/src/infra/http-client/index.ts @@ -0,0 +1,2 @@ +export * from './axios-adapter'; +export * from './http-client'; diff --git a/src/infra/http-server/currency.controller.ts b/src/infra/http-server/currency.controller.ts index 5e53d99b4..ac76a8d39 100644 --- a/src/infra/http-server/currency.controller.ts +++ b/src/infra/http-server/currency.controller.ts @@ -1,8 +1,10 @@ -import ConvertCurrency from "@/application/use-case/convert-currency"; -import CreateCurrencyRate, { CreateCurrencyRateDto } from "@/application/use-case/create-currency-rate"; -import { DeleteCurrencyRateByCode } from "@/application/use-case/delete-currency-rate-by-code"; -import GetCurrencyRateByCode from "@/application/use-case/get-currency-rate-by-code"; -import { HttpServer } from "@/infra/http-server/http-server"; +import { + ConvertCurrency, + CreateCurrencyRate, CreateCurrencyRateDto, + DeleteCurrencyRateByCode, + GetCurrencyRateByCode, +} from "@/application/use-case"; +import { HttpServer } from "@/infra/http-server"; export class CurrencyController { constructor( @@ -61,4 +63,6 @@ export class CurrencyController { }) } -} \ No newline at end of file +} + +export default CurrencyController; \ No newline at end of file diff --git a/src/infra/http-server/express-adapter.ts b/src/infra/http-server/express-adapter.ts index ef586031f..2ece5d6dc 100644 --- a/src/infra/http-server/express-adapter.ts +++ b/src/infra/http-server/express-adapter.ts @@ -1,4 +1,4 @@ -import { Controller, HttpServer, Method } from '@/infra/http-server/http-server'; +import { Controller, HttpServer, Method } from '@/infra/http-server'; import express, { Express } from 'express'; diff --git a/src/infra/http-server/hapi-adapter.ts b/src/infra/http-server/hapi-adapter.ts index 571da9cc4..8f2d65c26 100644 --- a/src/infra/http-server/hapi-adapter.ts +++ b/src/infra/http-server/hapi-adapter.ts @@ -1,4 +1,4 @@ -import { Controller, HttpServer, Method } from '@/infra/http-server/http-server'; +import { Controller, HttpServer, Method } from '@/infra/http-server'; import Hapi from '@hapi/hapi'; diff --git a/src/infra/http-server/index.ts b/src/infra/http-server/index.ts new file mode 100644 index 000000000..acdaa175f --- /dev/null +++ b/src/infra/http-server/index.ts @@ -0,0 +1,5 @@ +export * from './currency.controller'; +export * from './express-adapter'; +export * from './hapi-adapter'; +export * from './http-server'; + diff --git a/src/infra/repository/currency-rate-repository-api.ts b/src/infra/repository/currency-rate-repository-api.ts index 519604421..dfb69b1a9 100644 --- a/src/infra/repository/currency-rate-repository-api.ts +++ b/src/infra/repository/currency-rate-repository-api.ts @@ -1,7 +1,7 @@ -import CurrencyRateRepository from "@/application/repository/currency-rate-repository"; -import CurrencyRate from "@/domain/currency-rate"; -import CurrencyRateNotFoundError from "@/domain/errors/currency-rate-not-found.error"; -import CurrencyApiGateway from "../gateway/currency-api-gateway"; +import { CurrencyRateRepository } from "@/application/repository/"; +import { CurrencyRate } from "@/domain/entity/"; +import { CurrencyRateNotFoundError } from "@/domain/errors/"; +import { CurrencyApiGateway } from "@/infra/gateway"; export class CurrencyRateRepositoryApi implements CurrencyRateRepository { currencyMap: Map; @@ -46,4 +46,6 @@ export class CurrencyRateRepositoryApi implements CurrencyRateRepository { this.currencyMap.delete(code); } -} \ No newline at end of file +} + +export default CurrencyRateRepositoryApi; \ No newline at end of file diff --git a/src/infra/repository/currency-rate-repository-database.ts b/src/infra/repository/currency-rate-repository-database.ts index d06b206c9..c0a0051e5 100644 --- a/src/infra/repository/currency-rate-repository-database.ts +++ b/src/infra/repository/currency-rate-repository-database.ts @@ -1,7 +1,7 @@ -import CurrencyRateRepository from "@/application/repository/currency-rate-repository"; -import CurrencyRate from "@/domain/currency-rate"; -import CurrencyRateNotFoundError from "@/domain/errors/currency-rate-not-found.error"; -import { DatabaseConnection } from "@/infra/database/database-connection"; +import { CurrencyRateRepository } from "@/application/repository"; +import { CurrencyRate } from "@/domain/entity/"; +import { CurrencyRateNotFoundError } from "@/domain/errors"; +import { DatabaseConnection } from "@/infra/database"; export class CurrencyRateRepositoryDatabase implements CurrencyRateRepository { @@ -57,4 +57,6 @@ export class CurrencyRateRepositoryDatabase implements CurrencyRateRepository { `, [code]); } -} \ No newline at end of file +} + +export default CurrencyRateRepositoryDatabase; \ No newline at end of file diff --git a/src/infra/repository/currency-rate-repository-fake.ts b/src/infra/repository/currency-rate-repository-fake.ts index 8b9602ad5..9a656bd32 100644 --- a/src/infra/repository/currency-rate-repository-fake.ts +++ b/src/infra/repository/currency-rate-repository-fake.ts @@ -1,8 +1,8 @@ -import CurrencyRateRepository from "@/application/repository/currency-rate-repository"; -import CurrencyRate from "@/domain/currency-rate"; -import CurrencyRateNotFoundError from "@/domain/errors/currency-rate-not-found.error"; +import { CurrencyRateRepository } from "@/application/repository/"; +import { CurrencyRate } from "@/domain/entity/"; +import { CurrencyRateNotFoundError } from "@/domain/errors/"; -export default class CurrencyRateRepositoryFake implements CurrencyRateRepository { +export class CurrencyRateRepositoryFake implements CurrencyRateRepository { currencyRates: CurrencyRate[]; constructor() { @@ -39,4 +39,6 @@ export default class CurrencyRateRepositoryFake implements CurrencyRateRepositor async exists(code: string) { return this.currencyRates.some(currencyRate => currencyRate.code === code); } -} \ No newline at end of file +} + +export default CurrencyRateRepositoryFake; \ No newline at end of file diff --git a/src/infra/repository/index.ts b/src/infra/repository/index.ts new file mode 100644 index 000000000..033153c91 --- /dev/null +++ b/src/infra/repository/index.ts @@ -0,0 +1,3 @@ +export * from './currency-rate-repository-api'; +export * from './currency-rate-repository-database'; +export * from './currency-rate-repository-fake'; diff --git a/src/main/main-express-pg.ts b/src/main/main-express-pg.ts index 70b87b7ae..221237ab4 100644 --- a/src/main/main-express-pg.ts +++ b/src/main/main-express-pg.ts @@ -1,11 +1,12 @@ -import ConvertCurrency from "@/application/use-case/convert-currency"; -import CreateCurrencyRate from "@/application/use-case/create-currency-rate"; -import { DeleteCurrencyRateByCode } from "@/application/use-case/delete-currency-rate-by-code"; -import GetCurrencyRateByCode from "@/application/use-case/get-currency-rate-by-code"; -import { PgPromiseAdapter } from "@/infra/database/pgpromise-adapter"; -import { CurrencyController } from "@/infra/http-server/currency.controller"; -import { ExpressAdapter } from "@/infra/http-server/express-adapter"; -import { CurrencyRateRepositoryDatabase } from "@/infra/repository/currency-rate-repository-database"; +import { + ConvertCurrency, + CreateCurrencyRate, + DeleteCurrencyRateByCode, + GetCurrencyRateByCode, +} from "@/application/use-case/"; +import { PgPromiseAdapter } from "@/infra/database/"; +import { CurrencyController, ExpressAdapter } from "@/infra/http-server/"; +import { CurrencyRateRepositoryDatabase } from "@/infra/repository/"; const httpServer = new ExpressAdapter(); const connection = new PgPromiseAdapter(); diff --git a/src/main/main-express.ts b/src/main/main-express.ts index 726043016..6fa6ae1de 100644 --- a/src/main/main-express.ts +++ b/src/main/main-express.ts @@ -1,10 +1,11 @@ -import ConvertCurrency from "@/application/use-case/convert-currency"; -import CreateCurrencyRate from "@/application/use-case/create-currency-rate"; -import { DeleteCurrencyRateByCode } from "@/application/use-case/delete-currency-rate-by-code"; -import GetCurrencyRateByCode from "@/application/use-case/get-currency-rate-by-code"; -import { CurrencyController } from "@/infra/http-server/currency.controller"; -import { ExpressAdapter } from "@/infra/http-server/express-adapter"; -import CurrencyRateRepositoryFake from "@/infra/repository/currency-rate-repository-fake"; +import { + ConvertCurrency, + CreateCurrencyRate, + DeleteCurrencyRateByCode, + GetCurrencyRateByCode, +} from "@/application/use-case/"; +import { CurrencyController, ExpressAdapter } from "@/infra/http-server/"; +import { CurrencyRateRepositoryFake } from "@/infra/repository/"; const httpServer = new ExpressAdapter(); const currencyRateRepository = new CurrencyRateRepositoryFake(); diff --git a/src/main/main-gateway.ts b/src/main/main-gateway.ts index 1e1db9257..ca05b6bc0 100644 --- a/src/main/main-gateway.ts +++ b/src/main/main-gateway.ts @@ -1,12 +1,13 @@ -import ConvertCurrency from "@/application/use-case/convert-currency"; -import CreateCurrencyRate from "@/application/use-case/create-currency-rate"; -import { DeleteCurrencyRateByCode } from "@/application/use-case/delete-currency-rate-by-code"; -import GetCurrencyRateByCode from "@/application/use-case/get-currency-rate-by-code"; -import { CurrenciesApiGatewayOnline } from "@/infra/gateway/currency-api-gateway-online"; -import { AxiosAdapter } from "@/infra/http-client/axios-adapter"; -import { CurrencyController } from "@/infra/http-server/currency.controller"; -import { ExpressAdapter } from "@/infra/http-server/express-adapter"; -import { CurrencyRateRepositoryApi } from "@/infra/repository/currency-rate-repository-api"; +import { + ConvertCurrency, + CreateCurrencyRate, + DeleteCurrencyRateByCode, + GetCurrencyRateByCode, +} from "@/application/use-case/"; +import { CurrenciesApiGatewayOnline } from "@/infra/gateway"; +import { AxiosAdapter } from "@/infra/http-client"; +import { CurrencyController, ExpressAdapter } from "@/infra/http-server/"; +import { CurrencyRateRepositoryApi } from "@/infra/repository/"; const httpServer = new ExpressAdapter(); // const currencyGateway = new CurrenciesApiGatewayStatic() diff --git a/src/main/main-hapi.ts b/src/main/main-hapi.ts index 74953bc6d..574e95430 100644 --- a/src/main/main-hapi.ts +++ b/src/main/main-hapi.ts @@ -1,10 +1,11 @@ -import ConvertCurrency from "@/application/use-case/convert-currency"; -import CreateCurrencyRate from "@/application/use-case/create-currency-rate"; -import { DeleteCurrencyRateByCode } from "@/application/use-case/delete-currency-rate-by-code"; -import GetCurrencyRateByCode from "@/application/use-case/get-currency-rate-by-code"; -import { CurrencyController } from "@/infra/http-server/currency.controller"; -import { HapiAdapter } from "@/infra/http-server/hapi-adapter"; -import CurrencyRateRepositoryFake from "@/infra/repository/currency-rate-repository-fake"; +import { + ConvertCurrency, + CreateCurrencyRate, + DeleteCurrencyRateByCode, + GetCurrencyRateByCode, +} from "@/application/use-case/"; +import { CurrencyController, HapiAdapter } from "@/infra/http-server/"; +import { CurrencyRateRepositoryFake } from "@/infra/repository/"; const httpServer = new HapiAdapter(); const currencyRateRepository = new CurrencyRateRepositoryFake(); diff --git a/test/unity/convert-currency.test.ts b/test/unity/convert-currency.test.ts index 9cf615b10..18ca1818b 100644 --- a/test/unity/convert-currency.test.ts +++ b/test/unity/convert-currency.test.ts @@ -1,5 +1,5 @@ -import ConvertCurrency from "@/application/use-case/convert-currency"; -import CurrencyRateRepositoryFake from "@/infra/repository/currency-rate-repository-fake"; +import { ConvertCurrency } from "@/application/use-case"; +import { CurrencyRateRepositoryFake } from "@/infra/repository/"; describe('Currency Converter', () => { let currencyRateRepository: CurrencyRateRepositoryFake; diff --git a/test/unity/create-currency-rate.test.ts b/test/unity/create-currency-rate.test.ts index b13a30a73..22f627090 100644 --- a/test/unity/create-currency-rate.test.ts +++ b/test/unity/create-currency-rate.test.ts @@ -1,6 +1,5 @@ -import CreateCurrencyRate, { CreateCurrencyRateDto } from "@/application/use-case/create-currency-rate"; -import GetCurrencyRateByCode from "@/application/use-case/get-currency-rate-by-code"; -import CurrencyRateRepositoryFake from "@/infra/repository/currency-rate-repository-fake"; +import { CreateCurrencyRate, GetCurrencyRateByCode } from "@/application/use-case/"; +import { CurrencyRateRepositoryFake } from "@/infra/repository/"; describe('create-currency-rate', () => { @@ -29,7 +28,7 @@ describe('create-currency-rate', () => { it('should not create a currency with duplicated code', async () => { const currencyRateRepository = new CurrencyRateRepositoryFake(); const createCurrency = new CreateCurrencyRate(currencyRateRepository); - const createCurrencyInput: CreateCurrencyRateDto = { + const createCurrencyInput = { name: 'Any Currency', code: 'ANY', symbol: 'A$', diff --git a/test/unity/currenty-rate.test.ts b/test/unity/currenty-rate.test.ts index 80bc7212a..c7dba54d6 100644 --- a/test/unity/currenty-rate.test.ts +++ b/test/unity/currenty-rate.test.ts @@ -1,4 +1,4 @@ -import CurrencyRate from "@/domain/currency-rate"; +import { CurrencyRate } from "@/domain/entity/"; describe('Currency Rate', () => { diff --git a/test/unity/delete-currency-rate.test.ts b/test/unity/delete-currency-rate.test.ts index 5004d28ed..0d29d1ee5 100644 --- a/test/unity/delete-currency-rate.test.ts +++ b/test/unity/delete-currency-rate.test.ts @@ -1,7 +1,5 @@ -import CreateCurrencyRate from "@/application/use-case/create-currency-rate"; -import { DeleteCurrencyRateByCode } from "@/application/use-case/delete-currency-rate-by-code"; -import GetCurrencyRateByCode from "@/application/use-case/get-currency-rate-by-code"; -import CurrencyRateRepositoryFake from "@/infra/repository/currency-rate-repository-fake"; +import { CreateCurrencyRate, DeleteCurrencyRateByCode, GetCurrencyRateByCode } from "@/application/use-case/"; +import { CurrencyRateRepositoryFake } from "@/infra/repository/"; describe('delete-currency-rate', () => { diff --git a/test/unity/equivalent-rate-calculator.test.ts b/test/unity/equivalent-rate-calculator.test.ts index dbf90e57b..f9d0d6b57 100644 --- a/test/unity/equivalent-rate-calculator.test.ts +++ b/test/unity/equivalent-rate-calculator.test.ts @@ -1,4 +1,4 @@ -import { EquivalentRateCalculator } from "@/application/service/equivalent-rate-calculator"; +import { EquivalentRateCalculator } from "@/application/service/"; describe('equivalent-rate-calculator', () => { From 8496b470e5f942459960664d4d0255dc51ce4724 Mon Sep 17 00:00:00 2001 From: Leandro Reis Date: Thu, 13 Jun 2024 18:02:40 -0300 Subject: [PATCH 25/43] chore: configure Prettier and ESLint, format files - Installed and configured Prettier and ESLint for code formatting and linting. - Added .prettierrc and .eslintrc.json configuration files. - Integrated Prettier with ESLint to ensure consistent code style. - Reformatted all source files according to Prettier and ESLint rules. - Updated import paths and adjusted formatting in various modules. --- .prettierignore | 7 + .prettierrc | 7 + docker-compose.yml | 4 +- eslint.config.mjs | 14 + jest.config.js | 8 - jest.config.ts | 11 + package.json | 7 +- .../repository/currency-rate-repository.ts | 10 +- .../service/equivalent-rate-calculator.ts | 11 +- src/application/use-case/convert-currency.ts | 19 +- .../use-case/create-currency-rate.ts | 39 +- .../use-case/delete-currency-rate-by-code.ts | 8 +- .../use-case/get-currency-rate-by-code.ts | 10 +- src/domain/entity/currency-rate.ts | 33 +- .../errors/currency-rate-not-found.error.ts | 4 +- src/domain/errors/index.ts | 1 - src/domain/errors/invalid-param.error.ts | 2 +- src/domain/errors/not-found.error.ts | 2 +- src/domain/value-object/code.ts | 2 +- src/domain/value-object/decimal-points.ts | 2 +- src/domain/value-object/index.ts | 1 - src/domain/value-object/name.ts | 2 +- src/domain/value-object/rate.ts | 2 +- src/domain/value-object/symbol.ts | 2 +- src/index.ts | 2 +- src/infra/database/database-connection.ts | 9 +- src/infra/database/pgpromise-adapter.ts | 11 +- .../gateway/currency-api-gateway-online.ts | 23 +- .../gateway/currency-api-gateway-static.ts | 68 +- src/infra/gateway/currency-api-gateway.ts | 44 +- src/infra/http-client/axios-adapter.ts | 23 +- src/infra/http-client/http-client.ts | 20 +- src/infra/http-server/currency.controller.ts | 37 +- src/infra/http-server/express-adapter.ts | 22 +- src/infra/http-server/hapi-adapter.ts | 9 +- src/infra/http-server/http-server.ts | 12 +- src/infra/http-server/index.ts | 1 - .../currency-rate-repository-api.ts | 19 +- .../currency-rate-repository-database.ts | 58 +- .../currency-rate-repository-fake.ts | 28 +- src/main/main-express-pg.ts | 28 +- src/main/main-express.ts | 26 +- src/main/main-gateway.ts | 30 +- src/main/main-hapi.ts | 26 +- test/integration/api.test.ts | 34 +- test/unity/convert-currency.test.ts | 33 +- test/unity/create-currency-rate.test.ts | 49 +- test/unity/currenty-rate.test.ts | 56 +- test/unity/delete-currency-rate.test.ts | 54 +- test/unity/equivalent-rate-calculator.test.ts | 25 +- tsconfig.json | 2 +- yarn.lock | 596 +++++++++++++++++- 52 files changed, 1167 insertions(+), 386 deletions(-) create mode 100644 .prettierignore create mode 100644 .prettierrc create mode 100644 eslint.config.mjs delete mode 100644 jest.config.js create mode 100644 jest.config.ts diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 000000000..bbcd12cfd --- /dev/null +++ b/.prettierignore @@ -0,0 +1,7 @@ +node_modules +dist +coverage +README.md +README.pt.md +tsconfig.json +yarn.lock \ No newline at end of file diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 000000000..eba5a682d --- /dev/null +++ b/.prettierrc @@ -0,0 +1,7 @@ +{ + "semi": true, + "singleQuote": true, + "trailingComma": "es5", + "printWidth": 80, + "tabWidth": 2 +} diff --git a/docker-compose.yml b/docker-compose.yml index ba77c7a13..02a412e7e 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,10 +1,10 @@ -version: "3.9" +version: '3.9' services: db: image: postgres ports: - - "5432:5432" + - '5432:5432' environment: POSTGRES_USER: postgres POSTGRES_PASSWORD: 123456 diff --git a/eslint.config.mjs b/eslint.config.mjs new file mode 100644 index 000000000..cc55f42f4 --- /dev/null +++ b/eslint.config.mjs @@ -0,0 +1,14 @@ +import pluginJs from '@eslint/js'; +import eslintConfigPrettier from 'eslint-config-prettier'; +import tseslint from 'typescript-eslint'; + +export default [ + pluginJs.configs.recommended, + ...tseslint.configs.recommended, + eslintConfigPrettier, + { + rules: { + '@typescript-eslint/no-explicit-any': 'off', + }, + }, +]; diff --git a/jest.config.js b/jest.config.js deleted file mode 100644 index 2ab4265c9..000000000 --- a/jest.config.js +++ /dev/null @@ -1,8 +0,0 @@ -/** @type {import('ts-jest').JestConfigWithTsJest} */ -module.exports = { - preset: "ts-jest", - testEnvironment: "node", - moduleNameMapper: { - "^@/(.*)$": "/src/$1", - }, -}; diff --git a/jest.config.ts b/jest.config.ts new file mode 100644 index 000000000..79d1967cb --- /dev/null +++ b/jest.config.ts @@ -0,0 +1,11 @@ +import type { Config } from 'jest'; + +const config: Config = { + preset: 'ts-jest', + testEnvironment: 'node', + moduleNameMapper: { + '^@/(.*)$': '/src/$1', + }, +}; + +export default config; diff --git a/package.json b/package.json index 06978489c..c3854b452 100644 --- a/package.json +++ b/package.json @@ -23,13 +23,18 @@ "pg-promise": "^11.8.0" }, "devDependencies": { + "@eslint/js": "^9.4.0", "@types/express": "^4.17.21", "@types/jest": "^29.5.12", + "eslint": "9.x", + "eslint-config-prettier": "^9.1.0", "jest": "^29.7.0", + "prettier": "3.3.2", "ts-jest": "^29.1.4", "ts-node": "^10.9.2", "ts-node-dev": "^2.0.0", "tsconfig-paths": "^4.2.0", - "typescript": "^5.4.5" + "typescript": "^5.4.5", + "typescript-eslint": "^7.13.0" } } diff --git a/src/application/repository/currency-rate-repository.ts b/src/application/repository/currency-rate-repository.ts index 3d0f6c20c..7a89466cb 100644 --- a/src/application/repository/currency-rate-repository.ts +++ b/src/application/repository/currency-rate-repository.ts @@ -1,9 +1,9 @@ -import { CurrencyRate } from "@/domain/entity/"; +import { type CurrencyRate } from '@/domain/entity/'; export interface CurrencyRateRepository { - findByCode(code: string): Promise; - add(currencyRate: CurrencyRate): Promise; - delete(code: string): Promise; + findByCode: (code: string) => Promise; + add: (currencyRate: CurrencyRate) => Promise; + delete: (code: string) => Promise; } -export default CurrencyRateRepository; \ No newline at end of file +export default CurrencyRateRepository; diff --git a/src/application/service/equivalent-rate-calculator.ts b/src/application/service/equivalent-rate-calculator.ts index d158e7b90..0f2a64f29 100644 --- a/src/application/service/equivalent-rate-calculator.ts +++ b/src/application/service/equivalent-rate-calculator.ts @@ -1,8 +1,7 @@ -import { InvalidParamError } from "@/domain/errors"; +import { InvalidParamError } from '@/domain/errors'; export class EquivalentRateCalculator { - - constructor(readonly equivalentCurrencyRate: number) { } + constructor(readonly equivalentCurrencyRate: number) {} calculateRate(baseAmount: number, equivalentCurrencyAmount: number): number { if (baseAmount <= 0) { @@ -11,10 +10,10 @@ export class EquivalentRateCalculator { if (equivalentCurrencyAmount <= 0) { throw new InvalidParamError('Invalid param: equivalentCurrencyAmount'); } - const rate = baseAmount / equivalentCurrencyAmount * this.equivalentCurrencyRate; - return rate + const rate = + (baseAmount / equivalentCurrencyAmount) * this.equivalentCurrencyRate; + return rate; } - } export default EquivalentRateCalculator; diff --git a/src/application/use-case/convert-currency.ts b/src/application/use-case/convert-currency.ts index dce5173dc..036bf4f6b 100644 --- a/src/application/use-case/convert-currency.ts +++ b/src/application/use-case/convert-currency.ts @@ -1,18 +1,19 @@ -import { CurrencyRateRepository } from "@/application/repository"; +import { CurrencyRateRepository } from '@/application/repository'; export class ConvertCurrency { - constructor(readonly currencyRateRepository: CurrencyRateRepository) { - } + constructor(readonly currencyRateRepository: CurrencyRateRepository) {} async execute(input: ConvertCurrencyDto): Promise { - const exchangeFrom = await this.currencyRateRepository.findByCode(input.from); + const exchangeFrom = await this.currencyRateRepository.findByCode( + input.from + ); const exchangeTo = await this.currencyRateRepository.findByCode(input.to); const convertedAmount = exchangeFrom.convertTo(exchangeTo, input.amount); return { value: convertedAmount, symbol: exchangeTo.symbol, - decimalPoints: exchangeTo.decimalPoints - } + decimalPoints: exchangeTo.decimalPoints, + }; } } @@ -20,12 +21,12 @@ export type ConvertCurrencyDto = { from: string; to: string; amount: number; -} +}; export type ConvertCurrencyResponse = { value: number; symbol: string; decimalPoints: number; -} +}; -export default ConvertCurrency; \ No newline at end of file +export default ConvertCurrency; diff --git a/src/application/use-case/create-currency-rate.ts b/src/application/use-case/create-currency-rate.ts index 513e3c968..cfa1b5e5c 100644 --- a/src/application/use-case/create-currency-rate.ts +++ b/src/application/use-case/create-currency-rate.ts @@ -1,15 +1,30 @@ -import { CurrencyRateRepository } from "@/application/repository"; -import { EquivalentRateCalculator } from "@/application/service"; -import { CurrencyRate } from "@/domain/entity/"; +import { CurrencyRateRepository } from '@/application/repository'; +import { EquivalentRateCalculator } from '@/application/service'; +import { CurrencyRate } from '@/domain/entity/'; export class CreateCurrencyRate { - constructor(private currencyRateRepository: CurrencyRateRepository) { } + constructor(private currencyRateRepository: CurrencyRateRepository) {} - async execute(input: CreateCurrencyRateDto): Promise { - const equivalentCurrencyRate = await this.currencyRateRepository.findByCode(input.equivalentCurrencyCode.toUpperCase()); - const equivalentRateCalculator = new EquivalentRateCalculator(equivalentCurrencyRate.rate); - const rate = equivalentRateCalculator.calculateRate(input.baseAmount, input.equivalentCurrencyAmount) - const currencyRate = CurrencyRate.create(input.name, input.code, input.symbol, input.decimalPoints, rate); + async execute( + input: CreateCurrencyRateDto + ): Promise { + const equivalentCurrencyRate = await this.currencyRateRepository.findByCode( + input.equivalentCurrencyCode.toUpperCase() + ); + const equivalentRateCalculator = new EquivalentRateCalculator( + equivalentCurrencyRate.rate + ); + const rate = equivalentRateCalculator.calculateRate( + input.baseAmount, + input.equivalentCurrencyAmount + ); + const currencyRate = CurrencyRate.create( + input.name, + input.code, + input.symbol, + input.decimalPoints, + rate + ); await this.currencyRateRepository.add(currencyRate); return { currencyRateId: currencyRate.id }; } @@ -23,10 +38,10 @@ export type CreateCurrencyRateDto = { baseAmount: number; equivalentCurrencyCode: string; equivalentCurrencyAmount: number; -} +}; export type CreateCurrencyRateOutput = { currencyRateId: string; -} +}; -export default CreateCurrencyRate; \ No newline at end of file +export default CreateCurrencyRate; diff --git a/src/application/use-case/delete-currency-rate-by-code.ts b/src/application/use-case/delete-currency-rate-by-code.ts index c285d53c6..4ba511f70 100644 --- a/src/application/use-case/delete-currency-rate-by-code.ts +++ b/src/application/use-case/delete-currency-rate-by-code.ts @@ -1,7 +1,9 @@ -import { CurrencyRateRepository } from "@/application/repository"; +import { CurrencyRateRepository } from '@/application/repository'; export class DeleteCurrencyRateByCode { - constructor(private readonly currencyRateRepository: CurrencyRateRepository) { } + constructor( + private readonly currencyRateRepository: CurrencyRateRepository + ) {} async execute(code: string): Promise { if (code === 'USD') { @@ -15,4 +17,4 @@ export class DeleteCurrencyRateByCode { } } -export default DeleteCurrencyRateByCode; \ No newline at end of file +export default DeleteCurrencyRateByCode; diff --git a/src/application/use-case/get-currency-rate-by-code.ts b/src/application/use-case/get-currency-rate-by-code.ts index 175050192..f8bdd36cb 100644 --- a/src/application/use-case/get-currency-rate-by-code.ts +++ b/src/application/use-case/get-currency-rate-by-code.ts @@ -1,8 +1,7 @@ -import { CurrencyRateRepository } from "@/application/repository"; +import { CurrencyRateRepository } from '@/application/repository'; export class GetCurrencyRateByCode { - - constructor(readonly currencyRateRepository: CurrencyRateRepository) { } + constructor(readonly currencyRateRepository: CurrencyRateRepository) {} async execute(code: string): Promise { const currencyRate = await this.currencyRateRepository.findByCode(code); @@ -15,7 +14,6 @@ export class GetCurrencyRateByCode { rate: currencyRate.rate, }; } - } export type GetCurrencyRateByCodeOutput = { @@ -25,6 +23,6 @@ export type GetCurrencyRateByCodeOutput = { symbol: string; decimalPoints: number; rate: number; -} +}; -export default GetCurrencyRateByCode; \ No newline at end of file +export default GetCurrencyRateByCode; diff --git a/src/domain/entity/currency-rate.ts b/src/domain/entity/currency-rate.ts index 83261f15a..4e8117ddb 100644 --- a/src/domain/entity/currency-rate.ts +++ b/src/domain/entity/currency-rate.ts @@ -1,11 +1,17 @@ -import currency from "currency.js"; +import currency from 'currency.js'; -import { Code, DecimalPoints, Name, Rate, Symbol } from "@/domain/value-object"; +import { + Code, + DecimalPoints, + Symbol as MySymbol, + Name, + Rate, +} from '@/domain/value-object'; export class CurrencyRate { private _name: Name; private _code: Code; - private _symbol: Symbol; + private _symbol: MySymbol; private _decimalPoints: DecimalPoints; private _rate: Rate; @@ -15,16 +21,22 @@ export class CurrencyRate { code: string, symbol: string, decimalPoints: number, - rate: number, + rate: number ) { this._name = new Name(name); this._code = new Code(code); - this._symbol = new Symbol(symbol); + this._symbol = new MySymbol(symbol); this._decimalPoints = new DecimalPoints(decimalPoints); this._rate = new Rate(rate); } - static create(name: string, code: string, symbol: string, decimalPoints: number, rate: number): CurrencyRate { + static create( + name: string, + code: string, + symbol: string, + decimalPoints: number, + rate: number + ): CurrencyRate { const id = crypto.randomUUID(); return new CurrencyRate(id, name, code, symbol, decimalPoints, rate); } @@ -53,11 +65,12 @@ export class CurrencyRate { if (amount < 0) { throw new Error('Invalid amount'); } - const convertedAmount = amount * currencyRate.rate / this.rate; - const amountCurrency = currency(convertedAmount, { precision: currencyRate.decimalPoints }); + const convertedAmount = (amount * currencyRate.rate) / this.rate; + const amountCurrency = currency(convertedAmount, { + precision: currencyRate.decimalPoints, + }); return amountCurrency.value; } - } -export default CurrencyRate; \ No newline at end of file +export default CurrencyRate; diff --git a/src/domain/errors/currency-rate-not-found.error.ts b/src/domain/errors/currency-rate-not-found.error.ts index 000e6c366..89631d06a 100644 --- a/src/domain/errors/currency-rate-not-found.error.ts +++ b/src/domain/errors/currency-rate-not-found.error.ts @@ -1,4 +1,4 @@ -import NotFoundError from "./not-found.error"; +import NotFoundError from './not-found.error'; export class CurrencyRateNotFoundError extends NotFoundError { constructor(currencyRateIdOrCode: string) { @@ -7,4 +7,4 @@ export class CurrencyRateNotFoundError extends NotFoundError { } } -export default CurrencyRateNotFoundError; \ No newline at end of file +export default CurrencyRateNotFoundError; diff --git a/src/domain/errors/index.ts b/src/domain/errors/index.ts index 9081b991c..d65020034 100644 --- a/src/domain/errors/index.ts +++ b/src/domain/errors/index.ts @@ -1,4 +1,3 @@ export * from './currency-rate-not-found.error'; export * from './invalid-param.error'; export * from './not-found.error'; - diff --git a/src/domain/errors/invalid-param.error.ts b/src/domain/errors/invalid-param.error.ts index c22dae97d..7304e0340 100644 --- a/src/domain/errors/invalid-param.error.ts +++ b/src/domain/errors/invalid-param.error.ts @@ -5,4 +5,4 @@ export class InvalidParamError extends Error { } } -export default InvalidParamError; \ No newline at end of file +export default InvalidParamError; diff --git a/src/domain/errors/not-found.error.ts b/src/domain/errors/not-found.error.ts index 1920bcf7f..5f01ef096 100644 --- a/src/domain/errors/not-found.error.ts +++ b/src/domain/errors/not-found.error.ts @@ -5,4 +5,4 @@ export class NotFoundError extends Error { } } -export default NotFoundError; \ No newline at end of file +export default NotFoundError; diff --git a/src/domain/value-object/code.ts b/src/domain/value-object/code.ts index 48466d526..425de9364 100644 --- a/src/domain/value-object/code.ts +++ b/src/domain/value-object/code.ts @@ -13,4 +13,4 @@ export class Code { } } -export default Code; \ No newline at end of file +export default Code; diff --git a/src/domain/value-object/decimal-points.ts b/src/domain/value-object/decimal-points.ts index c922012ba..e7ae95231 100644 --- a/src/domain/value-object/decimal-points.ts +++ b/src/domain/value-object/decimal-points.ts @@ -13,4 +13,4 @@ export class DecimalPoints { } } -export default DecimalPoints; \ No newline at end of file +export default DecimalPoints; diff --git a/src/domain/value-object/index.ts b/src/domain/value-object/index.ts index a7c1197ff..6d8f1ea91 100644 --- a/src/domain/value-object/index.ts +++ b/src/domain/value-object/index.ts @@ -3,4 +3,3 @@ export * from './decimal-points'; export * from './name'; export * from './rate'; export * from './symbol'; - diff --git a/src/domain/value-object/name.ts b/src/domain/value-object/name.ts index 29b742525..5f8222128 100644 --- a/src/domain/value-object/name.ts +++ b/src/domain/value-object/name.ts @@ -13,4 +13,4 @@ export class Name { } } -export default Name; \ No newline at end of file +export default Name; diff --git a/src/domain/value-object/rate.ts b/src/domain/value-object/rate.ts index 7aefcd596..080a28d47 100644 --- a/src/domain/value-object/rate.ts +++ b/src/domain/value-object/rate.ts @@ -13,4 +13,4 @@ export class Rate { } } -export default Rate; \ No newline at end of file +export default Rate; diff --git a/src/domain/value-object/symbol.ts b/src/domain/value-object/symbol.ts index 4f84f49a2..a2c8e53cb 100644 --- a/src/domain/value-object/symbol.ts +++ b/src/domain/value-object/symbol.ts @@ -13,4 +13,4 @@ export class Symbol { } } -export default Symbol; \ No newline at end of file +export default Symbol; diff --git a/src/index.ts b/src/index.ts index 93655f130..bf9e730fe 100644 --- a/src/index.ts +++ b/src/index.ts @@ -2,4 +2,4 @@ async function main() { // This is the entry point of the application } -main().catch(console.error); \ No newline at end of file +main().catch(console.error); diff --git a/src/infra/database/database-connection.ts b/src/infra/database/database-connection.ts index e92374a48..afcb2c5df 100644 --- a/src/infra/database/database-connection.ts +++ b/src/infra/database/database-connection.ts @@ -1,6 +1,9 @@ export interface DatabaseConnection { - query(statement: string, params: TParams): Promise - close(): Promise + query( + statement: string, + params: TParams + ): Promise; + close(): Promise; } -export default DatabaseConnection; \ No newline at end of file +export default DatabaseConnection; diff --git a/src/infra/database/pgpromise-adapter.ts b/src/infra/database/pgpromise-adapter.ts index 4c077461e..dd21922a0 100644 --- a/src/infra/database/pgpromise-adapter.ts +++ b/src/infra/database/pgpromise-adapter.ts @@ -1,22 +1,21 @@ -import { DatabaseConnection } from "@/infra/database"; +import { DatabaseConnection } from '@/infra/database'; -import pgp from "pg-promise"; +import pgp from 'pg-promise'; export class PgPromiseAdapter implements DatabaseConnection { connection: any; constructor() { - this.connection = pgp()("postgres://postgres:123456@localhost:5432"); + this.connection = pgp()('postgres://postgres:123456@localhost:5432'); } query(statement: string, params: any): Promise { - return this.connection.query(statement, params) + return this.connection.query(statement, params); } close(): Promise { return this.connection.$pool.end(); } - } -export default PgPromiseAdapter; \ No newline at end of file +export default PgPromiseAdapter; diff --git a/src/infra/gateway/currency-api-gateway-online.ts b/src/infra/gateway/currency-api-gateway-online.ts index 1d3089634..4dad80086 100644 --- a/src/infra/gateway/currency-api-gateway-online.ts +++ b/src/infra/gateway/currency-api-gateway-online.ts @@ -1,20 +1,25 @@ -import { CurrenciesResponse, CurrencyApiGateway, Filters, RateResponse } from "@/infra/gateway/"; -import { HttpClient } from "@/infra/http-client"; +import { + CurrenciesResponse, + CurrencyApiGateway, + Filters, + RateResponse, +} from '@/infra/gateway/'; +import { HttpClient } from '@/infra/http-client'; export class CurrenciesApiGatewayOnline implements CurrencyApiGateway { constructor(private readonly httpClient: HttpClient) { httpClient.setSettings({ baseURL: 'https://api.currencyapi.com/v3/', headers: { - apiKey: 'cur_live_cuO1rc3J9p5XJE9QYRgyadj3ERuP57GKFUdt9yOh' - } - }) + apiKey: 'cur_live_cuO1rc3J9p5XJE9QYRgyadj3ERuP57GKFUdt9yOh', + }, + }); } getCurrencies(filters?: Filters): Promise { const params: { [key: string]: any } = { ...(filters?.currencies && { currencies: filters.currencies }), - ...(filters?.type && { type: filters.type }) + ...(filters?.type && { type: filters.type }), }; return this.httpClient.get('/currencies', { params }); } @@ -22,8 +27,8 @@ export class CurrenciesApiGatewayOnline implements CurrencyApiGateway { getRates(filters?: Filters): Promise { const params: { [key: string]: any } = { ...(filters?.currencies && { currencies: filters.currencies }), - ...(filters?.type && { type: filters.type }) + ...(filters?.type && { type: filters.type }), }; - return this.httpClient.get('/latest', { params }) + return this.httpClient.get('/latest', { params }); } -} \ No newline at end of file +} diff --git a/src/infra/gateway/currency-api-gateway-static.ts b/src/infra/gateway/currency-api-gateway-static.ts index 5b37d2089..e14abd27e 100644 --- a/src/infra/gateway/currency-api-gateway-static.ts +++ b/src/infra/gateway/currency-api-gateway-static.ts @@ -1,4 +1,9 @@ -import { CurrenciesResponse, CurrencyApiGateway, Filters, RateResponse } from "@/infra/gateway/"; +import { + CurrenciesResponse, + CurrencyApiGateway, + Filters, + RateResponse, +} from '@/infra/gateway/'; import { readFileSync } from 'fs'; import path from 'path'; @@ -14,55 +19,62 @@ export class CurrenciesApiGatewayStatic implements CurrencyApiGateway { } private async loadData(): Promise { - const currenciesFilePath = path.resolve(__dirname, './api.currencyapi-v3-currencies.json'); - const ratesFilePath = path.resolve(__dirname, './api.currencyapi-v3-latest.json'); - + const currenciesFilePath = path.resolve( + __dirname, + './api.currencyapi-v3-currencies.json' + ); + const ratesFilePath = path.resolve( + __dirname, + './api.currencyapi-v3-latest.json' + ); const currenciesData = readFileSync(currenciesFilePath, 'utf-8'); const ratesData = readFileSync(ratesFilePath, 'utf-8'); - const currenciesResponse: CurrenciesResponse = JSON.parse(currenciesData); const ratesResponse: RateResponse = JSON.parse(ratesData); - this.lastUpdatedAt = ratesResponse.meta.last_updated_at; - for (const [code, currency] of Object.entries(currenciesResponse.data)) { this.currenciesMap.set(code, currency); } - for (const [code, rate] of Object.entries(ratesResponse.data)) { this.ratesMap.set(code, rate); } } async getCurrencies(filters?: Filters): Promise { - const filteredCurrencies = Array.from(this.currenciesMap.entries()).reduce((acc, [code, currency]) => { - if ( - (!filters?.currencies || filters.currencies.includes(code)) && - (!filters?.type || currency.type === filters.type) - ) { - acc[code] = currency; - } - return acc; - }, {} as { [key: string]: any }); + const filteredCurrencies = Array.from(this.currenciesMap.entries()).reduce( + (acc, [code, currency]) => { + if ( + (!filters?.currencies || filters.currencies.includes(code)) && + (!filters?.type || currency.type === filters.type) + ) { + acc[code] = currency; + } + return acc; + }, + {} as { [key: string]: any } + ); return { data: filteredCurrencies }; } async getRates(filters?: Filters): Promise { - const filteredRates = Array.from(this.ratesMap.entries()).reduce((acc, [code, rate]) => { - if ( - (!filters?.currencies || filters.currencies.includes(code)) && - (!filters?.type || this.currenciesMap.get(code)?.type === filters.type) - ) { - acc[code] = rate; - } - return acc; - }, {} as { [key: string]: any }); - + const filteredRates = Array.from(this.ratesMap.entries()).reduce( + (acc, [code, rate]) => { + if ( + (!filters?.currencies || filters.currencies.includes(code)) && + (!filters?.type || + this.currenciesMap.get(code)?.type === filters.type) + ) { + acc[code] = rate; + } + return acc; + }, + {} as { [key: string]: any } + ); return { meta: { last_updated_at: this.lastUpdatedAt }, - data: filteredRates + data: filteredRates, }; } } diff --git a/src/infra/gateway/currency-api-gateway.ts b/src/infra/gateway/currency-api-gateway.ts index 99761a3be..9cb0e1c1c 100644 --- a/src/infra/gateway/currency-api-gateway.ts +++ b/src/infra/gateway/currency-api-gateway.ts @@ -3,36 +3,36 @@ export interface CurrencyApiGateway { getRates(filters?: Filters): Promise; } -export type Filters = { currencies?: string[], type?: CurrencyType } +export type Filters = { currencies?: string[]; type?: CurrencyType }; -export type CurrencyType = 'fiat' | 'metal' | 'crypto' +export type CurrencyType = 'fiat' | 'metal' | 'crypto'; export type CurrenciesResponse = { data: { [key: string]: { - symbol: string - name: string - symbol_native: string - decimal_digits: number - rounding: number - code: string - name_plural: string - type: CurrencyType - countries: string[] - } - } -} + symbol: string; + name: string; + symbol_native: string; + decimal_digits: number; + rounding: number; + code: string; + name_plural: string; + type: CurrencyType; + countries: string[]; + }; + }; +}; export type RateResponse = { meta: { - last_updated_at: Date - } + last_updated_at: Date; + }; data: { [key: string]: { - code: string - value: number - } - } -} + code: string; + value: number; + }; + }; +}; -export default CurrencyApiGateway; \ No newline at end of file +export default CurrencyApiGateway; diff --git a/src/infra/http-client/axios-adapter.ts b/src/infra/http-client/axios-adapter.ts index f2f42065a..e6b2afba5 100644 --- a/src/infra/http-client/axios-adapter.ts +++ b/src/infra/http-client/axios-adapter.ts @@ -1,31 +1,30 @@ -import { HttpClient, Options, Settings } from "@/infra/http-client" -import axios from "axios" +import { HttpClient, Options, Settings } from '@/infra/http-client'; +import axios from 'axios'; export class AxiosAdapter extends HttpClient { - api: axios.AxiosInstance + api: axios.AxiosInstance; constructor(settings: Settings = { baseURL: '', headers: {} }) { - super(settings) + super(settings); this.api = axios.create({ baseURL: settings.baseURL, - headers: settings.headers - }) + headers: settings.headers, + }); } async get(url: string, options?: Options): Promise { - const response = await this.api.get(url, options) - return response.data + const response = await this.api.get(url, options); + return response.data; } setSettings(settings: { [k in keyof Settings]?: Settings[k] }): void { - if (settings.baseURL) this.api.defaults.baseURL = settings.baseURL + if (settings.baseURL) this.api.defaults.baseURL = settings.baseURL; if (settings.headers) { for (const key in settings.headers) { - this.api.defaults.headers[key] = settings.headers[key] + this.api.defaults.headers[key] = settings.headers[key]; } } } - } -export default AxiosAdapter; \ No newline at end of file +export default AxiosAdapter; diff --git a/src/infra/http-client/http-client.ts b/src/infra/http-client/http-client.ts index b004c5411..4b80ff3ab 100644 --- a/src/infra/http-client/http-client.ts +++ b/src/infra/http-client/http-client.ts @@ -1,19 +1,19 @@ export abstract class HttpClient { constructor(protected readonly settings: Settings) { - this.setSettings(settings) + this.setSettings(settings); } - abstract setSettings(settings: { [k in keyof Settings]?: Settings[k] }): void - abstract get(url: string, options?: Options): Promise + abstract setSettings(settings: { [k in keyof Settings]?: Settings[k] }): void; + abstract get(url: string, options?: Options): Promise; } export type Settings = { - baseURL: string - headers: { [key: string]: string } -} + baseURL: string; + headers: { [key: string]: string }; +}; export type Options = { - params?: { [key: string]: any } - headers?: { [key: string]: string } -} + params?: { [key: string]: any }; + headers?: { [key: string]: string }; +}; -export default HttpClient \ No newline at end of file +export default HttpClient; diff --git a/src/infra/http-server/currency.controller.ts b/src/infra/http-server/currency.controller.ts index ac76a8d39..8e6728f8d 100644 --- a/src/infra/http-server/currency.controller.ts +++ b/src/infra/http-server/currency.controller.ts @@ -1,10 +1,11 @@ import { ConvertCurrency, - CreateCurrencyRate, CreateCurrencyRateDto, + CreateCurrencyRate, + CreateCurrencyRateDto, DeleteCurrencyRateByCode, GetCurrencyRateByCode, -} from "@/application/use-case"; -import { HttpServer } from "@/infra/http-server"; +} from '@/application/use-case'; +import { HttpServer } from '@/infra/http-server'; export class CurrencyController { constructor( @@ -14,12 +15,11 @@ export class CurrencyController { readonly getCurrencyRateByCode: GetCurrencyRateByCode, readonly deleteCurrencyRateByCode: DeleteCurrencyRateByCode ) { - httpServer.register('get', '/currencies/convert', async ({ query }) => { const input = { from: query.from.toUpperCase(), to: query.to.toUpperCase(), - amount: Number(query.amount) + amount: Number(query.amount), }; const output = await this.convertCurrency.execute(input); return { @@ -30,8 +30,8 @@ export class CurrencyController { decimalPoints: output.decimalPoints, symbol: output.symbol, convertedAmountFormatted: `${output.symbol} ${output.value.toFixed(output.decimalPoints)}`, - } - }) + }; + }); httpServer.register('post', '/currencies', async ({ body }) => { try { @@ -42,27 +42,26 @@ export class CurrencyController { decimalPoints: body.decimalPoints, baseAmount: body.baseAmount, equivalentCurrencyAmount: body.equivalentCurrencyAmount, - equivalentCurrencyCode: body.equivalentCurrencyCode - } + equivalentCurrencyCode: body.equivalentCurrencyCode, + }; const output = await this.createCurrencyRate.execute(input); - return output + return output; } catch (error: any) { - console.error(error.message) - throw error + console.error(error.message); + throw error; } - }) + }); httpServer.register('get', '/currencies/{code}', async ({ params }) => { const { code } = params; const output = await this.getCurrencyRateByCode.execute(code); - return output - }) + return output; + }); httpServer.register('delete', '/currencies/{code}', async ({ params }) => { const { code } = params; await this.deleteCurrencyRateByCode.execute(code); - return { message: 'Currency deleted' } - }) + return { message: 'Currency deleted' }; + }); } - } -export default CurrencyController; \ No newline at end of file +export default CurrencyController; diff --git a/src/infra/http-server/express-adapter.ts b/src/infra/http-server/express-adapter.ts index 2ece5d6dc..c9f0b4a3c 100644 --- a/src/infra/http-server/express-adapter.ts +++ b/src/infra/http-server/express-adapter.ts @@ -11,17 +11,23 @@ export class ExpressAdapter implements HttpServer { } register(method: Method, route: string, handler: Controller): void { - this.app[method](route.replace('}', '').replace('{', ':'), async (req, res) => { - try { - const result = await handler({ query: req.query, params: req.params, body: req.body }); - res.json(result); - } catch (error: any) { - res.status(500).json({ message: error.message }); + this.app[method]( + route.replace('}', '').replace('{', ':'), + async (req, res) => { + try { + const result = await handler({ + query: req.query, + params: req.params, + body: req.body, + }); + res.json(result); + } catch (error: any) { + res.status(500).json({ message: error.message }); + } } - }); + ); } - listen(port: number): void { this.app.listen(port, () => { console.log(`Server running on port ${port}`); diff --git a/src/infra/http-server/hapi-adapter.ts b/src/infra/http-server/hapi-adapter.ts index 8f2d65c26..e7c93df86 100644 --- a/src/infra/http-server/hapi-adapter.ts +++ b/src/infra/http-server/hapi-adapter.ts @@ -15,16 +15,19 @@ export class HapiAdapter implements HttpServer { path: route, handler: async (req, res) => { try { - const result = await handler({ query: req.query, params: req.params, body: req.payload }); + const result = await handler({ + query: req.query, + params: req.params, + body: req.payload, + }); return result; } catch (error: any) { return res.response({ message: error.message }).code(500); } }, - }) + }); } - listen(port: number): void { this.server.settings.port = port; this.server.start(); diff --git a/src/infra/http-server/http-server.ts b/src/infra/http-server/http-server.ts index 076e1423f..48a80298b 100644 --- a/src/infra/http-server/http-server.ts +++ b/src/infra/http-server/http-server.ts @@ -5,10 +5,12 @@ export interface HttpServer { export type Method = 'get' | 'post' | 'put' | 'delete'; -export type Controller = (args: ControllerParams) => Promise; +export type Controller = ( + args: ControllerParams +) => Promise; export type ControllerParams = { - query: { [key: string]: any } - params: { [key: string]: string }, - body: T -} \ No newline at end of file + query: { [key: string]: any }; + params: { [key: string]: string }; + body: T; +}; diff --git a/src/infra/http-server/index.ts b/src/infra/http-server/index.ts index acdaa175f..92fc21e8b 100644 --- a/src/infra/http-server/index.ts +++ b/src/infra/http-server/index.ts @@ -2,4 +2,3 @@ export * from './currency.controller'; export * from './express-adapter'; export * from './hapi-adapter'; export * from './http-server'; - diff --git a/src/infra/repository/currency-rate-repository-api.ts b/src/infra/repository/currency-rate-repository-api.ts index dfb69b1a9..c34012d94 100644 --- a/src/infra/repository/currency-rate-repository-api.ts +++ b/src/infra/repository/currency-rate-repository-api.ts @@ -1,7 +1,7 @@ -import { CurrencyRateRepository } from "@/application/repository/"; -import { CurrencyRate } from "@/domain/entity/"; -import { CurrencyRateNotFoundError } from "@/domain/errors/"; -import { CurrencyApiGateway } from "@/infra/gateway"; +import { CurrencyRateRepository } from '@/application/repository/'; +import { CurrencyRate } from '@/domain/entity/'; +import { CurrencyRateNotFoundError } from '@/domain/errors/'; +import { CurrencyApiGateway } from '@/infra/gateway'; export class CurrencyRateRepositoryApi implements CurrencyRateRepository { currencyMap: Map; @@ -14,12 +14,16 @@ export class CurrencyRateRepositoryApi implements CurrencyRateRepository { if (this.currencyMap.has(code)) { return this.currencyMap.get(code)!; } - const currencyResponse = await this.currencyGateway.getCurrencies({ currencies: [code] }); + const currencyResponse = await this.currencyGateway.getCurrencies({ + currencies: [code], + }); const currencyData = currencyResponse.data[code]; if (!currencyResponse.data[code] || !currencyResponse.data[code]) { throw new CurrencyRateNotFoundError(code); } - const rateResponse = await this.currencyGateway.getRates({ currencies: [code] }); + const rateResponse = await this.currencyGateway.getRates({ + currencies: [code], + }); const rateData = rateResponse.data[code]; const currency = new CurrencyRate( currencyData.code, @@ -45,7 +49,6 @@ export class CurrencyRateRepositoryApi implements CurrencyRateRepository { } this.currencyMap.delete(code); } - } -export default CurrencyRateRepositoryApi; \ No newline at end of file +export default CurrencyRateRepositoryApi; diff --git a/src/infra/repository/currency-rate-repository-database.ts b/src/infra/repository/currency-rate-repository-database.ts index c0a0051e5..79db794be 100644 --- a/src/infra/repository/currency-rate-repository-database.ts +++ b/src/infra/repository/currency-rate-repository-database.ts @@ -1,22 +1,23 @@ -import { CurrencyRateRepository } from "@/application/repository"; -import { CurrencyRate } from "@/domain/entity/"; -import { CurrencyRateNotFoundError } from "@/domain/errors"; -import { DatabaseConnection } from "@/infra/database"; +import { CurrencyRateRepository } from '@/application/repository'; +import { CurrencyRate } from '@/domain/entity/'; +import { CurrencyRateNotFoundError } from '@/domain/errors'; +import { DatabaseConnection } from '@/infra/database'; export class CurrencyRateRepositoryDatabase implements CurrencyRateRepository { - - constructor(private readonly _connection: DatabaseConnection) { - } + constructor(private readonly _connection: DatabaseConnection) {} async findByCode(code: string): Promise { - const [row] = await this._connection.query(` + const [row] = await this._connection.query( + ` SELECT c.id, c.name, c.code, c.symbol, c.decimal_digits, r.rate FROM currency c JOIN rate r ON c.id = r.currency_id WHERE c.code = $1 ORDER BY r.updated_at DESC LIMIT 1 - `, [code]); + `, + [code] + ); if (!row) { throw new CurrencyRateNotFoundError(code); } @@ -32,31 +33,36 @@ export class CurrencyRateRepositoryDatabase implements CurrencyRateRepository { } async add(currencyRate: CurrencyRate): Promise { - await this._connection.query(` + await this._connection.query( + ` INSERT INTO currency (id, name, code, symbol, decimal_digits) - VALUES ($1, $2, $3, $4, $5)`, [ - currencyRate.id, - currencyRate.name, - currencyRate.code, - currencyRate.symbol, - currencyRate.decimalPoints, - ]); + VALUES ($1, $2, $3, $4, $5)`, + [ + currencyRate.id, + currencyRate.name, + currencyRate.code, + currencyRate.symbol, + currencyRate.decimalPoints, + ] + ); - await this._connection.query(` + await this._connection.query( + ` INSERT INTO rate (currency_id, rate) - VALUES ($1, $2)`, [ - currencyRate.id, - currencyRate.rate, - ]); + VALUES ($1, $2)`, + [currencyRate.id, currencyRate.rate] + ); } async delete(code: string): Promise { - await this._connection.query(` + await this._connection.query( + ` DELETE FROM currency WHERE code = $1 - `, [code]); + `, + [code] + ); } - } -export default CurrencyRateRepositoryDatabase; \ No newline at end of file +export default CurrencyRateRepositoryDatabase; diff --git a/src/infra/repository/currency-rate-repository-fake.ts b/src/infra/repository/currency-rate-repository-fake.ts index 9a656bd32..0aec90cf7 100644 --- a/src/infra/repository/currency-rate-repository-fake.ts +++ b/src/infra/repository/currency-rate-repository-fake.ts @@ -1,6 +1,6 @@ -import { CurrencyRateRepository } from "@/application/repository/"; -import { CurrencyRate } from "@/domain/entity/"; -import { CurrencyRateNotFoundError } from "@/domain/errors/"; +import { CurrencyRateRepository } from '@/application/repository/'; +import { CurrencyRate } from '@/domain/entity/'; +import { CurrencyRateNotFoundError } from '@/domain/errors/'; export class CurrencyRateRepositoryFake implements CurrencyRateRepository { currencyRates: CurrencyRate[]; @@ -8,13 +8,15 @@ export class CurrencyRateRepositoryFake implements CurrencyRateRepository { constructor() { this.currencyRates = [ new CurrencyRate('1', 'US Dollar', 'USD', '$', 2, 1), - new CurrencyRate('2', 'Brazilian Real', 'BRL', 'R$', 2, 5.20), - new CurrencyRate('3', 'Euro', 'EUR', '€', 2, 0.92) + new CurrencyRate('2', 'Brazilian Real', 'BRL', 'R$', 2, 5.2), + new CurrencyRate('3', 'Euro', 'EUR', '€', 2, 0.92), ]; } async findByCode(code: string) { - const currencyRate = this.currencyRates.find(currencyRate => currencyRate.code === code); + const currencyRate = this.currencyRates.find( + (currencyRate) => currencyRate.code === code + ); if (!currencyRate) { throw new CurrencyRateNotFoundError(code); } @@ -24,21 +26,27 @@ export class CurrencyRateRepositoryFake implements CurrencyRateRepository { async add(currencyRate: CurrencyRate) { const exists = await this.exists(currencyRate.code); if (exists) { - throw new Error('Currency rate with code ' + currencyRate.code + ' already exists'); + throw new Error( + 'Currency rate with code ' + currencyRate.code + ' already exists' + ); } this.currencyRates.push(currencyRate); } async delete(code: string) { - const index = this.currencyRates.findIndex(currencyRate => currencyRate.code === code); + const index = this.currencyRates.findIndex( + (currencyRate) => currencyRate.code === code + ); if (index !== -1) { this.currencyRates.splice(index, 1); } } async exists(code: string) { - return this.currencyRates.some(currencyRate => currencyRate.code === code); + return this.currencyRates.some( + (currencyRate) => currencyRate.code === code + ); } } -export default CurrencyRateRepositoryFake; \ No newline at end of file +export default CurrencyRateRepositoryFake; diff --git a/src/main/main-express-pg.ts b/src/main/main-express-pg.ts index 221237ab4..ef541389d 100644 --- a/src/main/main-express-pg.ts +++ b/src/main/main-express-pg.ts @@ -3,17 +3,25 @@ import { CreateCurrencyRate, DeleteCurrencyRateByCode, GetCurrencyRateByCode, -} from "@/application/use-case/"; -import { PgPromiseAdapter } from "@/infra/database/"; -import { CurrencyController, ExpressAdapter } from "@/infra/http-server/"; -import { CurrencyRateRepositoryDatabase } from "@/infra/repository/"; +} from '@/application/use-case/'; +import { PgPromiseAdapter } from '@/infra/database/'; +import { CurrencyController, ExpressAdapter } from '@/infra/http-server/'; +import { CurrencyRateRepositoryDatabase } from '@/infra/repository/'; const httpServer = new ExpressAdapter(); const connection = new PgPromiseAdapter(); const currencyRateRepository = new CurrencyRateRepositoryDatabase(connection); -const currencyConverter = new ConvertCurrency(currencyRateRepository) -const createCurrencyRate = new CreateCurrencyRate(currencyRateRepository) -const getCurrencyRateByCode = new GetCurrencyRateByCode(currencyRateRepository) -const deleteCurrencyRateByCode = new DeleteCurrencyRateByCode(currencyRateRepository) -new CurrencyController(httpServer, currencyConverter, createCurrencyRate, getCurrencyRateByCode, deleteCurrencyRateByCode) -httpServer.listen(3000); \ No newline at end of file +const currencyConverter = new ConvertCurrency(currencyRateRepository); +const createCurrencyRate = new CreateCurrencyRate(currencyRateRepository); +const getCurrencyRateByCode = new GetCurrencyRateByCode(currencyRateRepository); +const deleteCurrencyRateByCode = new DeleteCurrencyRateByCode( + currencyRateRepository +); +new CurrencyController( + httpServer, + currencyConverter, + createCurrencyRate, + getCurrencyRateByCode, + deleteCurrencyRateByCode +); +httpServer.listen(3000); diff --git a/src/main/main-express.ts b/src/main/main-express.ts index 6fa6ae1de..d20d30046 100644 --- a/src/main/main-express.ts +++ b/src/main/main-express.ts @@ -3,15 +3,23 @@ import { CreateCurrencyRate, DeleteCurrencyRateByCode, GetCurrencyRateByCode, -} from "@/application/use-case/"; -import { CurrencyController, ExpressAdapter } from "@/infra/http-server/"; -import { CurrencyRateRepositoryFake } from "@/infra/repository/"; +} from '@/application/use-case/'; +import { CurrencyController, ExpressAdapter } from '@/infra/http-server/'; +import { CurrencyRateRepositoryFake } from '@/infra/repository/'; const httpServer = new ExpressAdapter(); const currencyRateRepository = new CurrencyRateRepositoryFake(); -const currencyConverter = new ConvertCurrency(currencyRateRepository) -const createCurrencyRate = new CreateCurrencyRate(currencyRateRepository) -const getCurrencyRateByCode = new GetCurrencyRateByCode(currencyRateRepository) -const deleteCurrencyRateByCode = new DeleteCurrencyRateByCode(currencyRateRepository) -new CurrencyController(httpServer, currencyConverter, createCurrencyRate, getCurrencyRateByCode, deleteCurrencyRateByCode) -httpServer.listen(3000); \ No newline at end of file +const currencyConverter = new ConvertCurrency(currencyRateRepository); +const createCurrencyRate = new CreateCurrencyRate(currencyRateRepository); +const getCurrencyRateByCode = new GetCurrencyRateByCode(currencyRateRepository); +const deleteCurrencyRateByCode = new DeleteCurrencyRateByCode( + currencyRateRepository +); +new CurrencyController( + httpServer, + currencyConverter, + createCurrencyRate, + getCurrencyRateByCode, + deleteCurrencyRateByCode +); +httpServer.listen(3000); diff --git a/src/main/main-gateway.ts b/src/main/main-gateway.ts index ca05b6bc0..020e12017 100644 --- a/src/main/main-gateway.ts +++ b/src/main/main-gateway.ts @@ -3,20 +3,28 @@ import { CreateCurrencyRate, DeleteCurrencyRateByCode, GetCurrencyRateByCode, -} from "@/application/use-case/"; -import { CurrenciesApiGatewayOnline } from "@/infra/gateway"; -import { AxiosAdapter } from "@/infra/http-client"; -import { CurrencyController, ExpressAdapter } from "@/infra/http-server/"; -import { CurrencyRateRepositoryApi } from "@/infra/repository/"; +} from '@/application/use-case/'; +import { CurrenciesApiGatewayOnline } from '@/infra/gateway'; +import { AxiosAdapter } from '@/infra/http-client'; +import { CurrencyController, ExpressAdapter } from '@/infra/http-server/'; +import { CurrencyRateRepositoryApi } from '@/infra/repository/'; const httpServer = new ExpressAdapter(); // const currencyGateway = new CurrenciesApiGatewayStatic() const httpClient = new AxiosAdapter(); const currencyGateway = new CurrenciesApiGatewayOnline(httpClient); const currencyRateRepository = new CurrencyRateRepositoryApi(currencyGateway); -const currencyConverter = new ConvertCurrency(currencyRateRepository) -const createCurrencyRate = new CreateCurrencyRate(currencyRateRepository) -const getCurrencyRateByCode = new GetCurrencyRateByCode(currencyRateRepository) -const deleteCurrencyRateByCode = new DeleteCurrencyRateByCode(currencyRateRepository) -new CurrencyController(httpServer, currencyConverter, createCurrencyRate, getCurrencyRateByCode, deleteCurrencyRateByCode) -httpServer.listen(3000); \ No newline at end of file +const currencyConverter = new ConvertCurrency(currencyRateRepository); +const createCurrencyRate = new CreateCurrencyRate(currencyRateRepository); +const getCurrencyRateByCode = new GetCurrencyRateByCode(currencyRateRepository); +const deleteCurrencyRateByCode = new DeleteCurrencyRateByCode( + currencyRateRepository +); +new CurrencyController( + httpServer, + currencyConverter, + createCurrencyRate, + getCurrencyRateByCode, + deleteCurrencyRateByCode +); +httpServer.listen(3000); diff --git a/src/main/main-hapi.ts b/src/main/main-hapi.ts index 574e95430..b196a4edb 100644 --- a/src/main/main-hapi.ts +++ b/src/main/main-hapi.ts @@ -3,15 +3,23 @@ import { CreateCurrencyRate, DeleteCurrencyRateByCode, GetCurrencyRateByCode, -} from "@/application/use-case/"; -import { CurrencyController, HapiAdapter } from "@/infra/http-server/"; -import { CurrencyRateRepositoryFake } from "@/infra/repository/"; +} from '@/application/use-case/'; +import { CurrencyController, HapiAdapter } from '@/infra/http-server/'; +import { CurrencyRateRepositoryFake } from '@/infra/repository/'; const httpServer = new HapiAdapter(); const currencyRateRepository = new CurrencyRateRepositoryFake(); -const currencyConverter = new ConvertCurrency(currencyRateRepository) -const createCurrencyRate = new CreateCurrencyRate(currencyRateRepository) -const getCurrencyRateByCode = new GetCurrencyRateByCode(currencyRateRepository) -const deleteCurrencyRateByCode = new DeleteCurrencyRateByCode(currencyRateRepository) -new CurrencyController(httpServer, currencyConverter, createCurrencyRate, getCurrencyRateByCode, deleteCurrencyRateByCode) -httpServer.listen(3000); \ No newline at end of file +const currencyConverter = new ConvertCurrency(currencyRateRepository); +const createCurrencyRate = new CreateCurrencyRate(currencyRateRepository); +const getCurrencyRateByCode = new GetCurrencyRateByCode(currencyRateRepository); +const deleteCurrencyRateByCode = new DeleteCurrencyRateByCode( + currencyRateRepository +); +new CurrencyController( + httpServer, + currencyConverter, + createCurrencyRate, + getCurrencyRateByCode, + deleteCurrencyRateByCode +); +httpServer.listen(3000); diff --git a/test/integration/api.test.ts b/test/integration/api.test.ts index f33c1fe82..93d7e9409 100644 --- a/test/integration/api.test.ts +++ b/test/integration/api.test.ts @@ -1,10 +1,12 @@ import axios from 'axios'; describe('Main API', () => { - it('should convert USD to BRL', async () => { const params = { from: 'USD', to: 'BRL', amount: 10 }; - const response = await axios.get('http://localhost:3000/currencies/convert', { params }); + const response = await axios.get( + 'http://localhost:3000/currencies/convert', + { params } + ); const output = response.data; expect(output.from).toBe('USD'); expect(output.to).toBe('BRL'); @@ -18,21 +20,23 @@ describe('Main API', () => { it('should convert BRL to USD', async () => { const brlResponse = await axios.get('http://localhost:3000/currencies/BRL'); const brlOutput = brlResponse.data; - const amount = brlOutput.rate * 2 + const amount = brlOutput.rate * 2; const params = { from: 'brl', to: 'usd', amount: amount }; - const response = await axios.get('http://localhost:3000/currencies/convert', { params }); + const response = await axios.get( + 'http://localhost:3000/currencies/convert', + { params } + ); const output = response.data; expect(output.from).toBe('BRL'); expect(output.to).toBe('USD'); expect(output.givenAmount).toBe(amount); - expect(output.convertedAmount).toBe(2.00); + expect(output.convertedAmount).toBe(2.0); expect(output.convertedAmountFormatted).toContain('$'); expect(output.decimalPoints).toBe(2); expect(output.symbol).toBe('$'); }); it('should create, get and delete a currency rate', async () => { - const payload = { name: 'Any Currency', code: 'ANYC', @@ -40,20 +44,23 @@ describe('Main API', () => { decimalPoints: 2, baseAmount: 2, equivalentCurrencyAmount: 1, - equivalentCurrencyCode: 'USD' + equivalentCurrencyCode: 'USD', }; await axios.post('http://localhost:3000/currencies', payload); - const getResponse = await axios.get(`http://localhost:3000/currencies/${payload.code}`); + const getResponse = await axios.get( + `http://localhost:3000/currencies/${payload.code}` + ); const getOutput = getResponse.data; expect(getOutput.name).toEqual(payload.name); expect(getOutput.code).toEqual(payload.code); expect(getOutput.symbol).toEqual(payload.symbol); expect(getOutput.decimalPoints).toEqual(payload.decimalPoints); expect(getOutput.rate).toEqual(2); - const deleteResponse = await axios.delete(`http://localhost:3000/currencies/${getOutput.code}`); + const deleteResponse = await axios.delete( + `http://localhost:3000/currencies/${getOutput.code}` + ); const deleteOutput = deleteResponse.data; expect(deleteOutput).toEqual({ message: 'Currency deleted' }); - }); it('should throw an error when trying to convert with invalid currency', async () => { @@ -61,7 +68,9 @@ describe('Main API', () => { try { await axios.get('http://localhost:3000/currencies/convert', { params }); } catch (error: any) { - expect(error.response.data).toEqual({ message: 'Currency rate not found: INVALID' }); + expect(error.response.data).toEqual({ + message: 'Currency rate not found: INVALID', + }); } }); @@ -73,5 +82,4 @@ describe('Main API', () => { expect(error.response.data).toEqual({ message: 'Invalid amount' }); } }); - -}); \ No newline at end of file +}); diff --git a/test/unity/convert-currency.test.ts b/test/unity/convert-currency.test.ts index 18ca1818b..6786882e5 100644 --- a/test/unity/convert-currency.test.ts +++ b/test/unity/convert-currency.test.ts @@ -1,5 +1,5 @@ -import { ConvertCurrency } from "@/application/use-case"; -import { CurrencyRateRepositoryFake } from "@/infra/repository/"; +import { ConvertCurrency } from '@/application/use-case'; +import { CurrencyRateRepositoryFake } from '@/infra/repository/'; describe('Currency Converter', () => { let currencyRateRepository: CurrencyRateRepositoryFake; @@ -15,19 +15,19 @@ describe('Currency Converter', () => { from: 'USD', to: 'BRL', amount: 1, - } + }; const result = await convertCurrency.execute(input); - expect(result.value).toBe(5.20); + expect(result.value).toBe(5.2); }); it('should convert BRL to USD', async () => { const input = { from: 'BRL', to: 'USD', - amount: 5.20, - } + amount: 5.2, + }; const result = await convertCurrency.execute(input); - expect(result.value).toBe(1.00); + expect(result.value).toBe(1.0); }); it('should convert USD to EUR', async () => { @@ -35,7 +35,7 @@ describe('Currency Converter', () => { from: 'USD', to: 'EUR', amount: 1, - } + }; const result = await convertCurrency.execute(input); expect(result.value).toBe(0.92); }); @@ -45,7 +45,7 @@ describe('Currency Converter', () => { from: 'EUR', to: 'BRL', amount: 1, - } + }; const result = await convertCurrency.execute(input); expect(result.value).toBe(5.65); }); @@ -55,8 +55,10 @@ describe('Currency Converter', () => { from: 'INVALID', to: 'BRL', amount: 1, - } - await expect(convertCurrency.execute(input)).rejects.toThrow('Currency rate not found'); + }; + await expect(convertCurrency.execute(input)).rejects.toThrow( + 'Currency rate not found' + ); }); it('should throw an error when the amount is invalid', async () => { @@ -64,8 +66,9 @@ describe('Currency Converter', () => { from: 'USD', to: 'BRL', amount: -1, - } - await expect(convertCurrency.execute(input)).rejects.toThrow('Invalid amount'); + }; + await expect(convertCurrency.execute(input)).rejects.toThrow( + 'Invalid amount' + ); }); - -}); \ No newline at end of file +}); diff --git a/test/unity/create-currency-rate.test.ts b/test/unity/create-currency-rate.test.ts index 22f627090..d06cf5647 100644 --- a/test/unity/create-currency-rate.test.ts +++ b/test/unity/create-currency-rate.test.ts @@ -1,27 +1,35 @@ -import { CreateCurrencyRate, GetCurrencyRateByCode } from "@/application/use-case/"; -import { CurrencyRateRepositoryFake } from "@/infra/repository/"; +import { + CreateCurrencyRate, + GetCurrencyRateByCode, +} from '@/application/use-case/'; +import { CurrencyRateRepositoryFake } from '@/infra/repository/'; describe('create-currency-rate', () => { - it('should create a currency', async () => { const currencyRateRepository = new CurrencyRateRepositoryFake(); const createCurrency = new CreateCurrencyRate(currencyRateRepository); const createCurrencyInput = { - name: "Grand Theft Auto Dollar", - symbol: "GTA$", - code: "GTA", + name: 'Grand Theft Auto Dollar', + symbol: 'GTA$', + code: 'GTA', decimalPoints: 2, - baseAmount: 1250000.00, - equivalentCurrencyCode: "BRL", - equivalentCurrencyAmount: 83.50, - } + baseAmount: 1250000.0, + equivalentCurrencyCode: 'BRL', + equivalentCurrencyAmount: 83.5, + }; await createCurrency.execute(createCurrencyInput); - const getCurrencyRateByCode = new GetCurrencyRateByCode(currencyRateRepository); - const getCurrencyOutput = await getCurrencyRateByCode.execute(createCurrencyInput.code); + const getCurrencyRateByCode = new GetCurrencyRateByCode( + currencyRateRepository + ); + const getCurrencyOutput = await getCurrencyRateByCode.execute( + createCurrencyInput.code + ); expect(getCurrencyOutput.name).toBe(createCurrencyInput.name); expect(getCurrencyOutput.symbol).toBe(createCurrencyInput.symbol); expect(getCurrencyOutput.code).toBe(createCurrencyInput.code); - expect(getCurrencyOutput.decimalPoints).toBe(createCurrencyInput.decimalPoints); + expect(getCurrencyOutput.decimalPoints).toBe( + createCurrencyInput.decimalPoints + ); expect(getCurrencyOutput.rate).toBe(77844.31137724551); }); @@ -33,12 +41,13 @@ describe('create-currency-rate', () => { code: 'ANY', symbol: 'A$', decimalPoints: 2, - baseAmount: 1.00, - equivalentCurrencyCode: "USD", - equivalentCurrencyAmount: 1.00, - } + baseAmount: 1.0, + equivalentCurrencyCode: 'USD', + equivalentCurrencyAmount: 1.0, + }; await createCurrency.execute(createCurrencyInput); - await expect(createCurrency.execute(createCurrencyInput)).rejects.toThrow('Currency rate with code ANY already exists'); + await expect(createCurrency.execute(createCurrencyInput)).rejects.toThrow( + 'Currency rate with code ANY already exists' + ); }); - -}); \ No newline at end of file +}); diff --git a/test/unity/currenty-rate.test.ts b/test/unity/currenty-rate.test.ts index c7dba54d6..ea1e9368c 100644 --- a/test/unity/currenty-rate.test.ts +++ b/test/unity/currenty-rate.test.ts @@ -1,7 +1,6 @@ -import { CurrencyRate } from "@/domain/entity/"; +import { CurrencyRate } from '@/domain/entity/'; describe('Currency Rate', () => { - it('should create a currency rate', () => { const currencyRate = CurrencyRate.create('US Dollar', 'USD', '$', 2, 1); expect(currencyRate).toBeDefined(); @@ -9,38 +8,61 @@ describe('Currency Rate', () => { it('should convert an amount to another currency', () => { const usdCurrencyRate = CurrencyRate.create('US Dollar', 'USD', '$', 2, 1); - const brlCurrencyRate = CurrencyRate.create('Brazilian Real', 'BRL', 'R$', 2, 5.2); - expect(usdCurrencyRate.convertTo(brlCurrencyRate, 1)).toBe(5.20); - expect(usdCurrencyRate.convertTo(brlCurrencyRate, 2)).toBe(10.40); + const brlCurrencyRate = CurrencyRate.create( + 'Brazilian Real', + 'BRL', + 'R$', + 2, + 5.2 + ); + expect(usdCurrencyRate.convertTo(brlCurrencyRate, 1)).toBe(5.2); + expect(usdCurrencyRate.convertTo(brlCurrencyRate, 2)).toBe(10.4); expect(brlCurrencyRate.convertTo(usdCurrencyRate, 5.2)).toBe(1); - expect(brlCurrencyRate.convertTo(usdCurrencyRate, 10.40)).toBe(2); + expect(brlCurrencyRate.convertTo(usdCurrencyRate, 10.4)).toBe(2); }); it('should throw an error when the rate is invalid', () => { - expect(() => CurrencyRate.create('US Dollar', 'USD', '$', 2, 0)).toThrow('Invalid rate'); - expect(() => CurrencyRate.create('US Dollar', 'USD', '$', 2, -1)).toThrow('Invalid rate'); + expect(() => CurrencyRate.create('US Dollar', 'USD', '$', 2, 0)).toThrow( + 'Invalid rate' + ); + expect(() => CurrencyRate.create('US Dollar', 'USD', '$', 2, -1)).toThrow( + 'Invalid rate' + ); }); it('should throw an error when the name is invalid', () => { - expect(() => CurrencyRate.create('', 'USD', '$', 2, 1)).toThrow('Invalid name'); + expect(() => CurrencyRate.create('', 'USD', '$', 2, 1)).toThrow( + 'Invalid name' + ); }); it('should throw an error when the code is invalid', () => { - expect(() => CurrencyRate.create('US Dollar', '', '$', 2, 1)).toThrow('Invalid code'); + expect(() => CurrencyRate.create('US Dollar', '', '$', 2, 1)).toThrow( + 'Invalid code' + ); }); it('should throw an error when the symbol is invalid', () => { - expect(() => CurrencyRate.create('US Dollar', 'USD', '', 2, 1)).toThrow('Invalid symbol'); + expect(() => CurrencyRate.create('US Dollar', 'USD', '', 2, 1)).toThrow( + 'Invalid symbol' + ); }); it('should throw an error when the decimal points is invalid', () => { - expect(() => CurrencyRate.create('US Dollar', 'USD', '$', -1, 1)).toThrow('Invalid decimal point'); - expect(() => CurrencyRate.create('US Dollar', 'USD', '$', 11, 1)).toThrow('Invalid decimal point'); + expect(() => CurrencyRate.create('US Dollar', 'USD', '$', -1, 1)).toThrow( + 'Invalid decimal point' + ); + expect(() => CurrencyRate.create('US Dollar', 'USD', '$', 11, 1)).toThrow( + 'Invalid decimal point' + ); }); it('should throw an error when the rate is invalid', () => { - expect(() => CurrencyRate.create('US Dollar', 'USD', '$', 2, 0)).toThrow('Invalid rate'); - expect(() => CurrencyRate.create('US Dollar', 'USD', '$', 2, -1)).toThrow('Invalid rate'); + expect(() => CurrencyRate.create('US Dollar', 'USD', '$', 2, 0)).toThrow( + 'Invalid rate' + ); + expect(() => CurrencyRate.create('US Dollar', 'USD', '$', 2, -1)).toThrow( + 'Invalid rate' + ); }); - -}); \ No newline at end of file +}); diff --git a/test/unity/delete-currency-rate.test.ts b/test/unity/delete-currency-rate.test.ts index 0d29d1ee5..fd6218deb 100644 --- a/test/unity/delete-currency-rate.test.ts +++ b/test/unity/delete-currency-rate.test.ts @@ -1,37 +1,53 @@ -import { CreateCurrencyRate, DeleteCurrencyRateByCode, GetCurrencyRateByCode } from "@/application/use-case/"; -import { CurrencyRateRepositoryFake } from "@/infra/repository/"; +import { + CreateCurrencyRate, + DeleteCurrencyRateByCode, + GetCurrencyRateByCode, +} from '@/application/use-case/'; +import { CurrencyRateRepositoryFake } from '@/infra/repository/'; describe('delete-currency-rate', () => { - it('should delete a currency rate by code', async () => { const currencyRateRepository = new CurrencyRateRepositoryFake(); const createCurrency = new CreateCurrencyRate(currencyRateRepository); const createCurrencyInput = { - name: "Any Currency", - symbol: "AC$", - code: "AYC", + name: 'Any Currency', + symbol: 'AC$', + code: 'AYC', decimalPoints: 2, - baseAmount: 1.00, - equivalentCurrencyCode: "USD", - equivalentCurrencyAmount: 1.00, - } + baseAmount: 1.0, + equivalentCurrencyCode: 'USD', + equivalentCurrencyAmount: 1.0, + }; await createCurrency.execute(createCurrencyInput); - const deleteCurrencyRate = new DeleteCurrencyRateByCode(currencyRateRepository); + const deleteCurrencyRate = new DeleteCurrencyRateByCode( + currencyRateRepository + ); await deleteCurrencyRate.execute(createCurrencyInput.code); - const getCurrencyRateByCode = new GetCurrencyRateByCode(currencyRateRepository); - await expect(getCurrencyRateByCode.execute(createCurrencyInput.code)).rejects.toThrow('Currency rate not found'); + const getCurrencyRateByCode = new GetCurrencyRateByCode( + currencyRateRepository + ); + await expect( + getCurrencyRateByCode.execute(createCurrencyInput.code) + ).rejects.toThrow('Currency rate not found'); }); it('should throw an error when trying to delete a currency rate that does not exist', async () => { const currencyRateRepository = new CurrencyRateRepositoryFake(); - const deleteCurrencyRate = new DeleteCurrencyRateByCode(currencyRateRepository); - await expect(deleteCurrencyRate.execute('ABC')).rejects.toThrow('Currency rate not found'); + const deleteCurrencyRate = new DeleteCurrencyRateByCode( + currencyRateRepository + ); + await expect(deleteCurrencyRate.execute('ABC')).rejects.toThrow( + 'Currency rate not found' + ); }); it('should throw an error when trying to delete USD currency rate', async () => { const currencyRateRepository = new CurrencyRateRepositoryFake(); - const deleteCurrencyRate = new DeleteCurrencyRateByCode(currencyRateRepository); - await expect(deleteCurrencyRate.execute('USD')).rejects.toThrow('Cannot delete USD currency rate'); + const deleteCurrencyRate = new DeleteCurrencyRateByCode( + currencyRateRepository + ); + await expect(deleteCurrencyRate.execute('USD')).rejects.toThrow( + 'Cannot delete USD currency rate' + ); }); - -}); \ No newline at end of file +}); diff --git a/test/unity/equivalent-rate-calculator.test.ts b/test/unity/equivalent-rate-calculator.test.ts index f9d0d6b57..c1b0ee4c8 100644 --- a/test/unity/equivalent-rate-calculator.test.ts +++ b/test/unity/equivalent-rate-calculator.test.ts @@ -1,27 +1,32 @@ -import { EquivalentRateCalculator } from "@/application/service/"; +import { EquivalentRateCalculator } from '@/application/service/'; describe('equivalent-rate-calculator', () => { - it('should calculate the equivalent rate of BRL in USD', async () => { const equivalentRateCalculator = new EquivalentRateCalculator(1); - expect(equivalentRateCalculator.calculateRate(5.20, 1.00)).toBe(5.20); - expect(equivalentRateCalculator.calculateRate(10.40, 2.00)).toBe(5.20); + expect(equivalentRateCalculator.calculateRate(5.2, 1.0)).toBe(5.2); + expect(equivalentRateCalculator.calculateRate(10.4, 2.0)).toBe(5.2); }); it('should calculate the equivalent rate of GTA$ in BRL', async () => { - const equivalentRateCalculator = new EquivalentRateCalculator(5.20); - const equivalentRate = equivalentRateCalculator.calculateRate(1250000.00, 83.50); + const equivalentRateCalculator = new EquivalentRateCalculator(5.2); + const equivalentRate = equivalentRateCalculator.calculateRate( + 1250000.0, + 83.5 + ); expect(equivalentRate).toBe(77844.31137724551); }); it('should throw an error when the base amount is invalid', async () => { const equivalentRateCalculator = new EquivalentRateCalculator(1); - expect(() => equivalentRateCalculator.calculateRate(-1, 1)).toThrow('Invalid param: baseAmount'); + expect(() => equivalentRateCalculator.calculateRate(-1, 1)).toThrow( + 'Invalid param: baseAmount' + ); }); it('should throw an error when the equivalent currency amount is invalid', async () => { const equivalentRateCalculator = new EquivalentRateCalculator(1); - expect(() => equivalentRateCalculator.calculateRate(1, -1)).toThrow('Invalid param: equivalentCurrencyAmount'); + expect(() => equivalentRateCalculator.calculateRate(1, -1)).toThrow( + 'Invalid param: equivalentCurrencyAmount' + ); }); - -}); \ No newline at end of file +}); diff --git a/tsconfig.json b/tsconfig.json index 414fb84ea..5bc823bec 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -109,5 +109,5 @@ "skipLibCheck": true /* Skip type checking all .d.ts files. */ }, "include": ["src/**/*", "test/**/*"], - "exclude": ["node_modules", "dist"], + "exclude": ["node_modules", "dist", "coverage"], } diff --git a/yarn.lock b/yarn.lock index aed921153..356d7600e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -304,6 +304,52 @@ dependencies: "@jridgewell/trace-mapping" "0.3.9" +"@eslint-community/eslint-utils@^4.2.0", "@eslint-community/eslint-utils@^4.4.0": + version "4.4.0" + resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz#a23514e8fb9af1269d5f7788aa556798d61c6b59" + integrity sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA== + dependencies: + eslint-visitor-keys "^3.3.0" + +"@eslint-community/regexpp@^4.10.0", "@eslint-community/regexpp@^4.6.1": + version "4.10.1" + resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.10.1.tgz#361461e5cb3845d874e61731c11cfedd664d83a0" + integrity sha512-Zm2NGpWELsQAD1xsJzGQpYfvICSsFkEpU0jxBjfdC6uNEWXcHnfs9hScFWtXVDVl+rBQJGrl4g1vcKIejpH9dA== + +"@eslint/config-array@^0.15.1": + version "0.15.1" + resolved "https://registry.yarnpkg.com/@eslint/config-array/-/config-array-0.15.1.tgz#1fa78b422d98f4e7979f2211a1fde137e26c7d61" + integrity sha512-K4gzNq+yymn/EVsXYmf+SBcBro8MTf+aXJZUphM96CdzUEr+ClGDvAbpmaEK+cGVigVXIgs9gNmvHAlrzzY5JQ== + dependencies: + "@eslint/object-schema" "^2.1.3" + debug "^4.3.1" + minimatch "^3.0.5" + +"@eslint/eslintrc@^3.1.0": + version "3.1.0" + resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-3.1.0.tgz#dbd3482bfd91efa663cbe7aa1f506839868207b6" + integrity sha512-4Bfj15dVJdoy3RfZmmo86RK1Fwzn6SstsvK9JS+BaVKqC6QQQQyXekNaC+g+LKNgkQ+2VhGAzm6hO40AhMR3zQ== + dependencies: + ajv "^6.12.4" + debug "^4.3.2" + espree "^10.0.1" + globals "^14.0.0" + ignore "^5.2.0" + import-fresh "^3.2.1" + js-yaml "^4.1.0" + minimatch "^3.1.2" + strip-json-comments "^3.1.1" + +"@eslint/js@9.4.0", "@eslint/js@^9.4.0": + version "9.4.0" + resolved "https://registry.yarnpkg.com/@eslint/js/-/js-9.4.0.tgz#96a2edd37ec0551ce5f9540705be23951c008a0c" + integrity sha512-fdI7VJjP3Rvc70lC4xkFXHB0fiPeojiL1PxVG6t1ZvXQrarj893PweuBTujxDUFk0Fxj4R7PIIAZ/aiiyZPZcg== + +"@eslint/object-schema@^2.1.3": + version "2.1.4" + resolved "https://registry.yarnpkg.com/@eslint/object-schema/-/object-schema-2.1.4.tgz#9e69f8bb4031e11df79e03db09f9dbbae1740843" + integrity sha512-BsWiH1yFGjXXS2yvrf5LyuoSIIbPrGUWob917o+BTKuZ7qJdxX8aJLRxs1fS9n6r7vESrq1OUqb68dANcFXuQQ== + "@hapi/accept@^6.0.1": version "6.0.3" resolved "https://registry.yarnpkg.com/@hapi/accept/-/accept-6.0.3.tgz#eef0800a4f89cd969da8e5d0311dc877c37279ab" @@ -554,6 +600,16 @@ "@hapi/bourne" "^3.0.0" "@hapi/hoek" "^11.0.2" +"@humanwhocodes/module-importer@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz#af5b2691a22b44be847b0ca81641c5fb6ad0172c" + integrity sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA== + +"@humanwhocodes/retry@^0.3.0": + version "0.3.0" + resolved "https://registry.yarnpkg.com/@humanwhocodes/retry/-/retry-0.3.0.tgz#6d86b8cb322660f03d3f0aa94b99bdd8e172d570" + integrity sha512-d2CGZR2o7fS6sWB7DG/3a95bGKQyHMACZ5aW8qGkkqQpUoZV6C0X7Pc7l4ZNMZkfNBf4VWNe9E1jRsf0G146Ew== + "@istanbuljs/load-nyc-config@^1.0.0": version "1.1.0" resolved "https://registry.yarnpkg.com/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz#fd3db1d59ecf7cf121e80650bb86712f9b55eced" @@ -802,6 +858,27 @@ "@jridgewell/resolve-uri" "^3.1.0" "@jridgewell/sourcemap-codec" "^1.4.14" +"@nodelib/fs.scandir@2.1.5": + version "2.1.5" + resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5" + integrity sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g== + dependencies: + "@nodelib/fs.stat" "2.0.5" + run-parallel "^1.1.9" + +"@nodelib/fs.stat@2.0.5", "@nodelib/fs.stat@^2.0.2": + version "2.0.5" + resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz#5bd262af94e9d25bd1e71b05deed44876a222e8b" + integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== + +"@nodelib/fs.walk@^1.2.3", "@nodelib/fs.walk@^1.2.8": + version "1.2.8" + resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz#e95737e8bb6746ddedf69c556953494f196fe69a" + integrity sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg== + dependencies: + "@nodelib/fs.scandir" "2.1.5" + fastq "^1.6.0" + "@sinclair/typebox@^0.27.8": version "0.27.8" resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.27.8.tgz#6667fac16c436b5434a387a34dedb013198f6e6e" @@ -1014,6 +1091,87 @@ dependencies: "@types/yargs-parser" "*" +"@typescript-eslint/eslint-plugin@7.13.0": + version "7.13.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.13.0.tgz#3cdeb5d44d051b21a9567535dd90702b2a42c6ff" + integrity sha512-FX1X6AF0w8MdVFLSdqwqN/me2hyhuQg4ykN6ZpVhh1ij/80pTvDKclX1sZB9iqex8SjQfVhwMKs3JtnnMLzG9w== + dependencies: + "@eslint-community/regexpp" "^4.10.0" + "@typescript-eslint/scope-manager" "7.13.0" + "@typescript-eslint/type-utils" "7.13.0" + "@typescript-eslint/utils" "7.13.0" + "@typescript-eslint/visitor-keys" "7.13.0" + graphemer "^1.4.0" + ignore "^5.3.1" + natural-compare "^1.4.0" + ts-api-utils "^1.3.0" + +"@typescript-eslint/parser@7.13.0": + version "7.13.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-7.13.0.tgz#9489098d68d57ad392f507495f2b82ce8b8f0a6b" + integrity sha512-EjMfl69KOS9awXXe83iRN7oIEXy9yYdqWfqdrFAYAAr6syP8eLEFI7ZE4939antx2mNgPRW/o1ybm2SFYkbTVA== + dependencies: + "@typescript-eslint/scope-manager" "7.13.0" + "@typescript-eslint/types" "7.13.0" + "@typescript-eslint/typescript-estree" "7.13.0" + "@typescript-eslint/visitor-keys" "7.13.0" + debug "^4.3.4" + +"@typescript-eslint/scope-manager@7.13.0": + version "7.13.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-7.13.0.tgz#6927d6451537ce648c6af67a2327378d4cc18462" + integrity sha512-ZrMCe1R6a01T94ilV13egvcnvVJ1pxShkE0+NDjDzH4nvG1wXpwsVI5bZCvE7AEDH1mXEx5tJSVR68bLgG7Dng== + dependencies: + "@typescript-eslint/types" "7.13.0" + "@typescript-eslint/visitor-keys" "7.13.0" + +"@typescript-eslint/type-utils@7.13.0": + version "7.13.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-7.13.0.tgz#4587282b5227a23753ea8b233805ecafc3924c76" + integrity sha512-xMEtMzxq9eRkZy48XuxlBFzpVMDurUAfDu5Rz16GouAtXm0TaAoTFzqWUFPPuQYXI/CDaH/Bgx/fk/84t/Bc9A== + dependencies: + "@typescript-eslint/typescript-estree" "7.13.0" + "@typescript-eslint/utils" "7.13.0" + debug "^4.3.4" + ts-api-utils "^1.3.0" + +"@typescript-eslint/types@7.13.0": + version "7.13.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-7.13.0.tgz#0cca95edf1f1fdb0cfe1bb875e121b49617477c5" + integrity sha512-QWuwm9wcGMAuTsxP+qz6LBBd3Uq8I5Nv8xb0mk54jmNoCyDspnMvVsOxI6IsMmway5d1S9Su2+sCKv1st2l6eA== + +"@typescript-eslint/typescript-estree@7.13.0": + version "7.13.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-7.13.0.tgz#4cc24fc155088ebf3b3adbad62c7e60f72c6de1c" + integrity sha512-cAvBvUoobaoIcoqox1YatXOnSl3gx92rCZoMRPzMNisDiM12siGilSM4+dJAekuuHTibI2hVC2fYK79iSFvWjw== + dependencies: + "@typescript-eslint/types" "7.13.0" + "@typescript-eslint/visitor-keys" "7.13.0" + debug "^4.3.4" + globby "^11.1.0" + is-glob "^4.0.3" + minimatch "^9.0.4" + semver "^7.6.0" + ts-api-utils "^1.3.0" + +"@typescript-eslint/utils@7.13.0": + version "7.13.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-7.13.0.tgz#f84e7e8aeceae945a9a3f40d077fd95915308004" + integrity sha512-jceD8RgdKORVnB4Y6BqasfIkFhl4pajB1wVxrF4akxD2QPM8GNYjgGwEzYS+437ewlqqrg7Dw+6dhdpjMpeBFQ== + dependencies: + "@eslint-community/eslint-utils" "^4.4.0" + "@typescript-eslint/scope-manager" "7.13.0" + "@typescript-eslint/types" "7.13.0" + "@typescript-eslint/typescript-estree" "7.13.0" + +"@typescript-eslint/visitor-keys@7.13.0": + version "7.13.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-7.13.0.tgz#2eb7ce8eb38c2b0d4a494d1fe1908e7071a1a353" + integrity sha512-nxn+dozQx+MK61nn/JP+M4eCkHDSxSLDpgE3WcQo0+fkjEolnaB5jswvIKC4K56By8MMgIho7f1PVxERHEo8rw== + dependencies: + "@typescript-eslint/types" "7.13.0" + eslint-visitor-keys "^3.4.3" + accepts@~1.3.8: version "1.3.8" resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.8.tgz#0bf0be125b67014adcb0b0921e62db7bffe16b2e" @@ -1022,16 +1180,31 @@ accepts@~1.3.8: mime-types "~2.1.34" negotiator "0.6.3" +acorn-jsx@^5.3.2: + version "5.3.2" + resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" + integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== + acorn-walk@^8.1.1: version "8.3.2" resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.3.2.tgz#7703af9415f1b6db9315d6895503862e231d34aa" integrity sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A== -acorn@^8.4.1: +acorn@^8.11.3, acorn@^8.4.1: version "8.11.3" resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.11.3.tgz#71e0b14e13a4ec160724b38fb7b0f233b1b81d7a" integrity sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg== +ajv@^6.12.4: + version "6.12.6" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" + integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== + dependencies: + fast-deep-equal "^3.1.1" + fast-json-stable-stringify "^2.0.0" + json-schema-traverse "^0.4.1" + uri-js "^4.2.2" + ansi-escapes@^4.2.1: version "4.3.2" resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.3.2.tgz#6b2291d1db7d98b6521d5f1efa42d0f3a9feb65e" @@ -1083,11 +1256,21 @@ argparse@^1.0.7: dependencies: sprintf-js "~1.0.2" +argparse@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" + integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== + array-flatten@1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2" integrity sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg== +array-union@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d" + integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw== + assert-options@0.8.1: version "0.8.1" resolved "https://registry.yarnpkg.com/assert-options/-/assert-options-0.8.1.tgz#f1df7cef7d0b8b29a3c091e6946287a4a9a45ab8" @@ -1203,6 +1386,13 @@ brace-expansion@^1.1.7: balanced-match "^1.0.0" concat-map "0.0.1" +brace-expansion@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-2.0.1.tgz#1edc459e0f0c548486ecf9fc99f2221364b9a0ae" + integrity sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA== + dependencies: + balanced-match "^1.0.0" + braces@^3.0.3, braces@~3.0.2: version "3.0.3" resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.3.tgz#490332f40919452272d55a8480adc0c441358789" @@ -1422,7 +1612,7 @@ create-require@^1.1.0: resolved "https://registry.yarnpkg.com/create-require/-/create-require-1.1.1.tgz#c1d7e8f1e5f6cfc9ff65f9cd352d37348756c333" integrity sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ== -cross-spawn@^7.0.3: +cross-spawn@^7.0.2, cross-spawn@^7.0.3: version "7.0.3" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== @@ -1455,11 +1645,23 @@ debug@^4.1.0, debug@^4.1.1, debug@^4.3.1: dependencies: ms "2.1.2" +debug@^4.3.2, debug@^4.3.4: + version "4.3.5" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.5.tgz#e83444eceb9fedd4a1da56d671ae2446a01a6e1e" + integrity sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg== + dependencies: + ms "2.1.2" + dedent@^1.0.0: version "1.5.3" resolved "https://registry.yarnpkg.com/dedent/-/dedent-1.5.3.tgz#99aee19eb9bae55a67327717b6e848d0bf777e5a" integrity sha512-NHQtfOOW68WD8lgypbLA5oT+Bt0xXJhiYvoR6SmmNXZfpzOGXwdKWmcwG8N7PwVVWV3eF/68nmD9BaJSsTBhyQ== +deep-is@^0.1.3: + version "0.1.4" + resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831" + integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ== + deepmerge@^4.2.2: version "4.3.1" resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.3.1.tgz#44b5f2147cd3b00d4b56137685966f26fd25dd4a" @@ -1504,6 +1706,13 @@ diff@^4.0.1: resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d" integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A== +dir-glob@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-3.0.1.tgz#56dbf73d992a4a93ba1584f4534063fd2e41717f" + integrity sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA== + dependencies: + path-type "^4.0.0" + dynamic-dedupe@^0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/dynamic-dedupe/-/dynamic-dedupe-0.3.0.tgz#06e44c223f5e4e94d78ef9db23a6515ce2f962a1" @@ -1575,11 +1784,112 @@ escape-string-regexp@^2.0.0: resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz#a30304e99daa32e23b2fd20f51babd07cffca344" integrity sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w== +escape-string-regexp@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" + integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== + +eslint-config-prettier@^9.1.0: + version "9.1.0" + resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-9.1.0.tgz#31af3d94578645966c082fcb71a5846d3c94867f" + integrity sha512-NSWl5BFQWEPi1j4TjVNItzYV7dZXZ+wP6I6ZhrBGpChQhZRUaElihE9uRRkcbRnNb76UMKDF3r+WTmNcGPKsqw== + +eslint-scope@^8.0.1: + version "8.0.1" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-8.0.1.tgz#a9601e4b81a0b9171657c343fb13111688963cfc" + integrity sha512-pL8XjgP4ZOmmwfFE8mEhSxA7ZY4C+LWyqjQ3o4yWkkmD0qcMT9kkW3zWHOczhWcjTSgqycYAgwSlXvZltv65og== + dependencies: + esrecurse "^4.3.0" + estraverse "^5.2.0" + +eslint-visitor-keys@^3.3.0, eslint-visitor-keys@^3.4.3: + version "3.4.3" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz#0cd72fe8550e3c2eae156a96a4dddcd1c8ac5800" + integrity sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag== + +eslint-visitor-keys@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-4.0.0.tgz#e3adc021aa038a2a8e0b2f8b0ce8f66b9483b1fb" + integrity sha512-OtIRv/2GyiF6o/d8K7MYKKbXrOUBIK6SfkIRM4Z0dY3w+LiQ0vy3F57m0Z71bjbyeiWFiHJ8brqnmE6H6/jEuw== + +eslint@9.x: + version "9.4.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-9.4.0.tgz#79150c3610ae606eb131f1d648d5f43b3d45f3cd" + integrity sha512-sjc7Y8cUD1IlwYcTS9qPSvGjAC8Ne9LctpxKKu3x/1IC9bnOg98Zy6GxEJUfr1NojMgVPlyANXYns8oE2c1TAA== + dependencies: + "@eslint-community/eslint-utils" "^4.2.0" + "@eslint-community/regexpp" "^4.6.1" + "@eslint/config-array" "^0.15.1" + "@eslint/eslintrc" "^3.1.0" + "@eslint/js" "9.4.0" + "@humanwhocodes/module-importer" "^1.0.1" + "@humanwhocodes/retry" "^0.3.0" + "@nodelib/fs.walk" "^1.2.8" + ajv "^6.12.4" + chalk "^4.0.0" + cross-spawn "^7.0.2" + debug "^4.3.2" + escape-string-regexp "^4.0.0" + eslint-scope "^8.0.1" + eslint-visitor-keys "^4.0.0" + espree "^10.0.1" + esquery "^1.4.2" + esutils "^2.0.2" + fast-deep-equal "^3.1.3" + file-entry-cache "^8.0.0" + find-up "^5.0.0" + glob-parent "^6.0.2" + ignore "^5.2.0" + imurmurhash "^0.1.4" + is-glob "^4.0.0" + is-path-inside "^3.0.3" + 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.3" + strip-ansi "^6.0.1" + text-table "^0.2.0" + +espree@^10.0.1: + version "10.0.1" + resolved "https://registry.yarnpkg.com/espree/-/espree-10.0.1.tgz#600e60404157412751ba4a6f3a2ee1a42433139f" + integrity sha512-MWkrWZbJsL2UwnjxTX3gG8FneachS/Mwg7tdGXce011sJd5b0JG54vat5KHnfSBODZ3Wvzd2WnjxyzsRoVv+ww== + dependencies: + acorn "^8.11.3" + acorn-jsx "^5.3.2" + eslint-visitor-keys "^4.0.0" + esprima@^4.0.0: version "4.0.1" resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== +esquery@^1.4.2: + version "1.5.0" + resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.5.0.tgz#6ce17738de8577694edd7361c57182ac8cb0db0b" + integrity sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg== + dependencies: + estraverse "^5.1.0" + +esrecurse@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.3.0.tgz#7ad7964d679abb28bee72cec63758b1c5d2c9921" + integrity sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag== + dependencies: + estraverse "^5.2.0" + +estraverse@^5.1.0, estraverse@^5.2.0: + version "5.3.0" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.3.0.tgz#2eea5290702f26ab8fe5370370ff86c965d21123" + integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA== + +esutils@^2.0.2: + version "2.0.3" + resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" + integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== + etag@~1.8.1: version "1.8.1" resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" @@ -1653,11 +1963,39 @@ express@^4.19.2: utils-merge "1.0.1" vary "~1.1.2" -fast-json-stable-stringify@2.x, fast-json-stable-stringify@^2.1.0: +fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: + version "3.1.3" + resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" + integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== + +fast-glob@^3.2.9: + version "3.3.2" + resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.3.2.tgz#a904501e57cfdd2ffcded45e99a54fef55e46129" + integrity sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow== + 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" + +fast-json-stable-stringify@2.x, fast-json-stable-stringify@^2.0.0, fast-json-stable-stringify@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== +fast-levenshtein@^2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" + integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw== + +fastq@^1.6.0: + version "1.17.1" + resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.17.1.tgz#2a523f07a4e7b1e81a42b91b8bf2254107753b47" + integrity sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w== + dependencies: + reusify "^1.0.4" + fb-watchman@^2.0.0: version "2.0.2" resolved "https://registry.yarnpkg.com/fb-watchman/-/fb-watchman-2.0.2.tgz#e9524ee6b5c77e9e5001af0f85f3adbb8623255c" @@ -1665,6 +2003,13 @@ fb-watchman@^2.0.0: dependencies: bser "2.1.1" +file-entry-cache@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-8.0.0.tgz#7787bddcf1131bffb92636c69457bbc0edd6d81f" + integrity sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ== + dependencies: + flat-cache "^4.0.0" + fill-range@^7.1.1: version "7.1.1" resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.1.1.tgz#44265d3cac07e3ea7dc247516380643754a05292" @@ -1693,6 +2038,27 @@ find-up@^4.0.0, find-up@^4.1.0: locate-path "^5.0.0" path-exists "^4.0.0" +find-up@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc" + integrity sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng== + dependencies: + locate-path "^6.0.0" + path-exists "^4.0.0" + +flat-cache@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-4.0.1.tgz#0ece39fcb14ee012f4b0410bd33dd9c1f011127c" + integrity sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw== + dependencies: + flatted "^3.2.9" + keyv "^4.5.4" + +flatted@^3.2.9: + version "3.3.1" + resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.3.1.tgz#21db470729a6734d4997002f439cb308987f567a" + integrity sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw== + follow-redirects@^1.15.6: version "1.15.6" resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.6.tgz#7f815c0cda4249c74ff09e95ef97c23b5fd0399b" @@ -1763,13 +2129,20 @@ get-stream@^6.0.0: resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7" integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg== -glob-parent@~5.1.2: +glob-parent@^5.1.2, glob-parent@~5.1.2: version "5.1.2" resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== dependencies: is-glob "^4.0.1" +glob-parent@^6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-6.0.2.tgz#6d237d99083950c79290f24c7642a3de9a28f9e3" + integrity sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A== + dependencies: + is-glob "^4.0.3" + glob@^7.1.3, glob@^7.1.4: version "7.2.3" resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" @@ -1787,6 +2160,23 @@ globals@^11.1.0: resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== +globals@^14.0.0: + version "14.0.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-14.0.0.tgz#898d7413c29babcf6bafe56fcadded858ada724e" + integrity sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ== + +globby@^11.1.0: + version "11.1.0" + resolved "https://registry.yarnpkg.com/globby/-/globby-11.1.0.tgz#bd4be98bb042f83d796f7e3811991fbe82a0d34b" + integrity sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g== + dependencies: + array-union "^2.1.0" + dir-glob "^3.0.1" + fast-glob "^3.2.9" + ignore "^5.2.0" + merge2 "^1.4.1" + slash "^3.0.0" + gopd@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.0.1.tgz#29ff76de69dac7489b7c0918a5788e56477c332c" @@ -1799,6 +2189,11 @@ graceful-fs@^4.2.9: resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== +graphemer@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/graphemer/-/graphemer-1.4.0.tgz#fb2f1d55e0e3a1849aeffc90c4fa0dd53a0e66c6" + integrity sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag== + has-flag@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" @@ -1861,6 +2256,19 @@ iconv-lite@0.4.24: dependencies: safer-buffer ">= 2.1.2 < 3" +ignore@^5.2.0, ignore@^5.3.1: + version "5.3.1" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.1.tgz#5073e554cd42c5b33b394375f538b8593e34d4ef" + integrity sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw== + +import-fresh@^3.2.1: + version "3.3.0" + resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b" + integrity sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw== + dependencies: + parent-module "^1.0.0" + resolve-from "^4.0.0" + import-local@^3.0.2: version "3.1.0" resolved "https://registry.yarnpkg.com/import-local/-/import-local-3.1.0.tgz#b4479df8a5fd44f6cdce24070675676063c95cb4" @@ -1926,7 +2334,7 @@ is-generator-fn@^2.0.0: resolved "https://registry.yarnpkg.com/is-generator-fn/-/is-generator-fn-2.1.0.tgz#7d140adc389aaf3011a8f2a2a4cfa6faadffb118" integrity sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ== -is-glob@^4.0.1, is-glob@~4.0.1: +is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3, is-glob@~4.0.1: version "4.0.3" resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== @@ -1938,6 +2346,11 @@ is-number@^7.0.0: resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== +is-path-inside@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-3.0.3.tgz#d231362e53a07ff2b0e0ea7fed049161ffd16283" + integrity sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ== + is-stream@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.1.tgz#fac1e3d53b97ad5a9d0ae9cef2389f5810a5c077" @@ -2372,21 +2785,50 @@ js-yaml@^3.13.1: argparse "^1.0.7" esprima "^4.0.0" +js-yaml@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" + integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== + dependencies: + argparse "^2.0.1" + jsesc@^2.5.1: version "2.5.2" resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4" integrity sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA== +json-buffer@3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.1.tgz#9338802a30d3b6605fbe0613e094008ca8c05a13" + integrity sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ== + json-parse-even-better-errors@^2.3.0: version "2.3.1" resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d" integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w== +json-schema-traverse@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" + integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== + +json-stable-stringify-without-jsonify@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" + integrity sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw== + json5@^2.2.2, json5@^2.2.3: version "2.2.3" resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283" integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== +keyv@^4.5.4: + version "4.5.4" + resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.5.4.tgz#a879a99e29452f942439f2a405e3af8b31d4de93" + integrity sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw== + dependencies: + json-buffer "3.0.1" + kleur@^3.0.3: version "3.0.3" resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e" @@ -2397,6 +2839,14 @@ leven@^3.1.0: resolved "https://registry.yarnpkg.com/leven/-/leven-3.1.0.tgz#77891de834064cccba82ae7842bb6b14a13ed7f2" integrity sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A== +levn@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/levn/-/levn-0.4.1.tgz#ae4562c007473b932a6200d403268dd2fffc6ade" + integrity sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ== + dependencies: + prelude-ls "^1.2.1" + type-check "~0.4.0" + lines-and-columns@^1.1.6: version "1.2.4" resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632" @@ -2409,11 +2859,23 @@ locate-path@^5.0.0: dependencies: p-locate "^4.1.0" +locate-path@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-6.0.0.tgz#55321eb309febbc59c4801d931a72452a681d286" + integrity sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw== + dependencies: + p-locate "^5.0.0" + lodash.memoize@4.x: version "4.1.2" resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" integrity sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag== +lodash.merge@^4.6.2: + version "4.6.2" + resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" + integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== + lru-cache@^5.1.1: version "5.1.1" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920" @@ -2455,6 +2917,11 @@ merge-stream@^2.0.0: resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== +merge2@^1.3.0, merge2@^1.4.1: + version "1.4.1" + resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" + integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== + methods@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" @@ -2490,13 +2957,20 @@ mimic-fn@^2.1.0: resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== -minimatch@^3.0.4, minimatch@^3.1.1: +minimatch@^3.0.4, minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2: version "3.1.2" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== dependencies: brace-expansion "^1.1.7" +minimatch@^9.0.4: + version "9.0.4" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.4.tgz#8e49c731d1749cbec05050ee5145147b32496a51" + integrity sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw== + dependencies: + brace-expansion "^2.0.1" + minimist@^1.2.6: version "1.2.8" resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c" @@ -2580,6 +3054,18 @@ onetime@^5.1.2: dependencies: mimic-fn "^2.1.0" +optionator@^0.9.3: + version "0.9.4" + resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.4.tgz#7ea1c1a5d91d764fb282139c88fe11e182a3a734" + integrity sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g== + dependencies: + deep-is "^0.1.3" + fast-levenshtein "^2.0.6" + levn "^0.4.1" + prelude-ls "^1.2.1" + type-check "^0.4.0" + word-wrap "^1.2.5" + p-limit@^2.2.0: version "2.3.0" resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1" @@ -2587,7 +3073,7 @@ p-limit@^2.2.0: dependencies: p-try "^2.0.0" -p-limit@^3.1.0: +p-limit@^3.0.2, p-limit@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b" integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ== @@ -2601,11 +3087,25 @@ p-locate@^4.1.0: dependencies: p-limit "^2.2.0" +p-locate@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-5.0.0.tgz#83c8315c6785005e3bd021839411c9e110e6d834" + integrity sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw== + dependencies: + p-limit "^3.0.2" + p-try@^2.0.0: version "2.2.0" resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== +parent-module@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2" + integrity sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g== + dependencies: + callsites "^3.0.0" + parse-json@^5.2.0: version "5.2.0" resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-5.2.0.tgz#c76fc66dee54231c962b22bcc8a72cf2f99753cd" @@ -2646,6 +3146,11 @@ path-to-regexp@0.1.7: resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" integrity sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ== +path-type@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" + integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== + pg-cloudflare@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/pg-cloudflare/-/pg-cloudflare-1.1.1.tgz#e6d5833015b170e23ae819e8c5d7eaedb472ca98" @@ -2761,6 +3266,16 @@ postgres-interval@^1.1.0: dependencies: xtend "^4.0.0" +prelude-ls@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" + integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== + +prettier@3.3.2: + version "3.3.2" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.3.2.tgz#03ff86dc7c835f2d2559ee76876a3914cec4a90a" + integrity sha512-rAVeHYMcv8ATV5d508CFdn+8/pHPpXeIid1DdrPwXnaAdH7cqjVbpJaT5eq4yRAFU/lsbwYwSF/n5iNrdJHPQA== + pretty-format@^29.0.0, pretty-format@^29.7.0: version "29.7.0" resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-29.7.0.tgz#ca42c758310f365bfa71a0bda0a807160b776812" @@ -2791,6 +3306,11 @@ proxy-from-env@^1.1.0: resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2" integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg== +punycode@^2.1.0: + version "2.3.1" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.1.tgz#027422e2faec0b25e1549c3e1bd8309b9133b6e5" + integrity sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg== + pure-rand@^6.0.0: version "6.1.0" resolved "https://registry.yarnpkg.com/pure-rand/-/pure-rand-6.1.0.tgz#d173cf23258231976ccbdb05247c9787957604f2" @@ -2803,6 +3323,11 @@ qs@6.11.0: dependencies: side-channel "^1.0.4" +queue-microtask@^1.2.2: + version "1.2.3" + resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" + integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== + range-parser@~1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031" @@ -2842,6 +3367,11 @@ resolve-cwd@^3.0.0: dependencies: resolve-from "^5.0.0" +resolve-from@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" + integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== + resolve-from@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-5.0.0.tgz#c35225843df8f776df21c57557bc087e9dfdfc69" @@ -2861,6 +3391,11 @@ resolve@^1.0.0, resolve@^1.20.0: path-parse "^1.0.7" supports-preserve-symlinks-flag "^1.0.0" +reusify@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" + integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== + rimraf@^2.6.1: version "2.7.1" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec" @@ -2868,6 +3403,13 @@ rimraf@^2.6.1: dependencies: glob "^7.1.3" +run-parallel@^1.1.9: + version "1.2.0" + resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee" + integrity sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA== + dependencies: + queue-microtask "^1.2.2" + safe-buffer@5.2.1: version "5.2.1" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" @@ -2883,7 +3425,7 @@ semver@^6.3.0, semver@^6.3.1: resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== -semver@^7.5.3, semver@^7.5.4: +semver@^7.5.3, semver@^7.5.4, semver@^7.6.0: version "7.6.2" resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.2.tgz#1e3b34759f896e8f14d6134732ce798aeb0c6e13" integrity sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w== @@ -3103,6 +3645,11 @@ test-exclude@^6.0.0: glob "^7.1.4" minimatch "^3.0.4" +text-table@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" + integrity sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw== + tmpl@1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/tmpl/-/tmpl-1.0.5.tgz#8683e0b902bb9c20c4f726e3c0b69f36518c07cc" @@ -3130,6 +3677,11 @@ tree-kill@^1.2.2: resolved "https://registry.yarnpkg.com/tree-kill/-/tree-kill-1.2.2.tgz#4ca09a9092c88b73a7cdc5e8a01b507b0790a0cc" integrity sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A== +ts-api-utils@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/ts-api-utils/-/ts-api-utils-1.3.0.tgz#4b490e27129f1e8e686b45cc4ab63714dc60eea1" + integrity sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ== + ts-jest@^29.1.4: version "29.1.4" resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-29.1.4.tgz#26f8a55ce31e4d2ef7a1fd47dc7fa127e92793ef" @@ -3198,6 +3750,13 @@ tsconfig@^7.0.0: strip-bom "^3.0.0" strip-json-comments "^2.0.0" +type-check@^0.4.0, type-check@~0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1" + integrity sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew== + dependencies: + prelude-ls "^1.2.1" + type-detect@4.0.8: version "4.0.8" resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c" @@ -3216,6 +3775,15 @@ type-is@~1.6.18: media-typer "0.3.0" mime-types "~2.1.24" +typescript-eslint@^7.13.0: + version "7.13.0" + resolved "https://registry.yarnpkg.com/typescript-eslint/-/typescript-eslint-7.13.0.tgz#bfd6f139b61e12d171af8621869785cb3b29f1b7" + integrity sha512-upO0AXxyBwJ4BbiC6CRgAJKtGYha2zw4m1g7TIVPSonwYEuf7vCicw3syjS1OxdDMTz96sZIXl3Jx3vWJLLKFw== + dependencies: + "@typescript-eslint/eslint-plugin" "7.13.0" + "@typescript-eslint/parser" "7.13.0" + "@typescript-eslint/utils" "7.13.0" + typescript@^5.4.5: version "5.4.5" resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.4.5.tgz#42ccef2c571fdbd0f6718b1d1f5e6e5ef006f611" @@ -3239,6 +3807,13 @@ update-browserslist-db@^1.0.13: escalade "^3.1.2" picocolors "^1.0.1" +uri-js@^4.2.2: + version "4.4.1" + resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e" + integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg== + dependencies: + punycode "^2.1.0" + utils-merge@1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" @@ -3277,6 +3852,11 @@ which@^2.0.1: dependencies: isexe "^2.0.0" +word-wrap@^1.2.5: + version "1.2.5" + resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.5.tgz#d2c45c6dd4fbce621a66f136cbe328afd0410b34" + integrity sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA== + wrap-ansi@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" From 5155775779aa386687c31df105e4ed19568d4674 Mon Sep 17 00:00:00 2001 From: Leandro Reis Date: Thu, 13 Jun 2024 20:36:40 -0300 Subject: [PATCH 26/43] fix: update GetCurrencyRateByCode to use uppercase code for consistency --- src/application/use-case/get-currency-rate-by-code.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/application/use-case/get-currency-rate-by-code.ts b/src/application/use-case/get-currency-rate-by-code.ts index f8bdd36cb..23ab56256 100644 --- a/src/application/use-case/get-currency-rate-by-code.ts +++ b/src/application/use-case/get-currency-rate-by-code.ts @@ -4,7 +4,9 @@ export class GetCurrencyRateByCode { constructor(readonly currencyRateRepository: CurrencyRateRepository) {} async execute(code: string): Promise { - const currencyRate = await this.currencyRateRepository.findByCode(code); + const currencyRate = await this.currencyRateRepository.findByCode( + code.toUpperCase() + ); return { id: currencyRate.id, name: currencyRate.name, From ea7fda8acc4e5a1a6254aff2f64c9926900525bb Mon Sep 17 00:00:00 2001 From: Leandro Reis Date: Thu, 13 Jun 2024 20:41:40 -0300 Subject: [PATCH 27/43] feat: Add swagger documentation for API endpoints - Integrate swagger-ui-express library to serve API documentation - Add swagger.json file to define API specifications - Configure Express server to serve the Swagger UI at /api-docs endpoint --- package.json | 4 +- src/infra/http-server/currency.controller.ts | 38 +-- src/infra/http-server/express-adapter.ts | 7 + src/infra/http-server/swagger.json | 245 +++++++++++++++++++ tsconfig.json | 2 +- yarn.lock | 22 +- 6 files changed, 297 insertions(+), 21 deletions(-) create mode 100644 src/infra/http-server/swagger.json diff --git a/package.json b/package.json index c3854b452..d191865f9 100644 --- a/package.json +++ b/package.json @@ -20,12 +20,14 @@ "crypto": "^1.0.1", "currency.js": "^2.0.4", "express": "^4.19.2", - "pg-promise": "^11.8.0" + "pg-promise": "^11.8.0", + "swagger-ui-express": "^5.0.1" }, "devDependencies": { "@eslint/js": "^9.4.0", "@types/express": "^4.17.21", "@types/jest": "^29.5.12", + "@types/swagger-ui-express": "^4.1.6", "eslint": "9.x", "eslint-config-prettier": "^9.1.0", "jest": "^29.7.0", diff --git a/src/infra/http-server/currency.controller.ts b/src/infra/http-server/currency.controller.ts index 8e6728f8d..52dccf8a3 100644 --- a/src/infra/http-server/currency.controller.ts +++ b/src/infra/http-server/currency.controller.ts @@ -15,24 +15,6 @@ export class CurrencyController { readonly getCurrencyRateByCode: GetCurrencyRateByCode, readonly deleteCurrencyRateByCode: DeleteCurrencyRateByCode ) { - httpServer.register('get', '/currencies/convert', async ({ query }) => { - const input = { - from: query.from.toUpperCase(), - to: query.to.toUpperCase(), - amount: Number(query.amount), - }; - const output = await this.convertCurrency.execute(input); - return { - from: input.from, - to: input.to, - givenAmount: input.amount, - convertedAmount: output.value, - decimalPoints: output.decimalPoints, - symbol: output.symbol, - convertedAmountFormatted: `${output.symbol} ${output.value.toFixed(output.decimalPoints)}`, - }; - }); - httpServer.register('post', '/currencies', async ({ body }) => { try { const input: CreateCurrencyRateDto = { @@ -51,11 +33,31 @@ export class CurrencyController { throw error; } }); + + httpServer.register('get', '/currencies/convert', async ({ query }) => { + const input = { + from: query.from.toUpperCase(), + to: query.to.toUpperCase(), + amount: Number(query.amount), + }; + const output = await this.convertCurrency.execute(input); + return { + from: input.from, + to: input.to, + givenAmount: input.amount, + convertedAmount: output.value, + decimalPoints: output.decimalPoints, + symbol: output.symbol, + convertedAmountFormatted: `${output.symbol} ${output.value.toFixed(output.decimalPoints)}`, + }; + }); + httpServer.register('get', '/currencies/{code}', async ({ params }) => { const { code } = params; const output = await this.getCurrencyRateByCode.execute(code); return output; }); + httpServer.register('delete', '/currencies/{code}', async ({ params }) => { const { code } = params; await this.deleteCurrencyRateByCode.execute(code); diff --git a/src/infra/http-server/express-adapter.ts b/src/infra/http-server/express-adapter.ts index c9f0b4a3c..1577d0637 100644 --- a/src/infra/http-server/express-adapter.ts +++ b/src/infra/http-server/express-adapter.ts @@ -1,4 +1,6 @@ import { Controller, HttpServer, Method } from '@/infra/http-server'; +import swaggerDocument from '@/infra/http-server/swagger.json'; +import SwaggerUi from 'swagger-ui-express'; import express, { Express } from 'express'; @@ -8,6 +10,11 @@ export class ExpressAdapter implements HttpServer { constructor() { this.app = express(); this.app.use(express.json()); + this.app.use( + '/api-docs', + SwaggerUi.serve, + SwaggerUi.setup(swaggerDocument) + ); } register(method: Method, route: string, handler: Controller): void { diff --git a/src/infra/http-server/swagger.json b/src/infra/http-server/swagger.json new file mode 100644 index 000000000..228237744 --- /dev/null +++ b/src/infra/http-server/swagger.json @@ -0,0 +1,245 @@ +{ + "openapi": "3.1.0", + "info": { + "title": "API de Conversão Monetária", + "description": "API de Conversão Monetária para o desafio Bravo da empresa hurbcom", + "contact": { + "email": "leandro.e.reis@gmail.com", + "url": "https://www.linkedin.com/in/leandroepr/" + }, + "license": { + "name": "Apache 2.0", + "url": "http://www.apache.org/licenses/LICENSE-2.0.html" + }, + "version": "1.0.0" + }, + "servers": [ + { + "url": "http://localhost:3000/", + "description": "Servidor Local" + } + ], + "tags": [ + { + "name": "Currencies", + "description": "Operações com moedas" + } + ], + "basePath": "/api/v1", + "paths": { + "/currencies": { + "post": { + "tags": ["Currencies"], + "summary": "Adiciona uma nova moeda", + "description": "Adiciona uma nova moeda ao sistema", + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "name": { "type": "string" }, + "code": { "type": "string" }, + "symbol": { "type": "string" }, + "baseAmount": { "type": "number" }, + "equivalentCurrencyAmount": { "type": "number" }, + "equivalentCurrencyCode": { "type": "string" } + } + } + } + } + }, + "responses": { + "200": { + "description": "Moeda adicionada com sucesso", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "currencyId": { "type": "string" } + } + } + } + } + }, + "400": { + "description": "Erro na requisição", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { "type": "string" } + } + } + } + } + } + } + } + }, + "/currencies/convert": { + "get": { + "tags": ["Currencies"], + "summary": "Conversor de moedas", + "description": "Converte uma moeda para outra", + "parameters": [ + { + "name": "from", + "in": "query", + "description": "Moeda de origem", + "required": true, + "schema": { "type": "string" } + }, + { + "name": "to", + "in": "query", + "description": "Moeda de destino", + "required": true, + "schema": { "type": "string" } + }, + { + "name": "amount", + "in": "query", + "description": "Valor a ser convertido", + "required": true, + "schema": { "type": "number" } + } + ], + "responses": { + "200": { + "description": "Conversão realizada com sucesso", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "from": { "type": "string" }, + "to": { "type": "string" }, + "givenAmount": { "type": "number" }, + "convertedAmount": { "type": "number" }, + "convertedAmountFormatted": { "type": "string" }, + "decimalPoints": { "type": "number" }, + "symbol": { "type": "string" } + } + } + } + } + }, + "400": { + "description": "Erro na requisição", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { "type": "string" } + } + } + } + } + } + } + } + }, + "/currencies/{code}": { + "get": { + "tags": ["Currencies"], + "summary": "Busca uma moeda", + "description": "Busca uma moeda pelo código", + "parameters": [ + { + "name": "code", + "in": "path", + "description": "Código da moeda", + "required": true, + "schema": { "type": "string" } + } + ], + "responses": { + "200": { + "description": "Moeda encontrada", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Currency" + } + } + } + }, + "404": { + "description": "Moeda não encontrada", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { "type": "string" } + } + } + } + } + } + } + }, + "delete": { + "tags": ["Currencies"], + "summary": "Remove uma moeda", + "description": "Remove uma moeda pelo código", + "parameters": [ + { + "name": "code", + "in": "path", + "description": "Código da moeda", + "required": true, + "schema": { "type": "string" } + } + ], + "responses": { + "200": { + "description": "Moeda removida com sucesso", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { "type": "string" } + } + } + } + } + }, + "404": { + "description": "Moeda não encontrada", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { "type": "string" } + } + } + } + } + } + } + } + } + }, + "components": { + "schemas": { + "Currency": { + "type": "object", + "properties": { + "id": { "type": "string" }, + "name": { "type": "string" }, + "code": { "type": "string" }, + "symbol": { "type": "string" }, + "decimalPoints": { "type": "number", "format": "integer" }, + "rate": { "type": "number", "format": "double" } + } + } + } + } +} diff --git a/tsconfig.json b/tsconfig.json index 5bc823bec..57f2f84ec 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -41,7 +41,7 @@ // "resolvePackageJsonExports": true, /* Use the package.json 'exports' field when resolving package imports. */ // "resolvePackageJsonImports": true, /* Use the package.json 'imports' field when resolving imports. */ // "customConditions": [], /* Conditions to set in addition to the resolver-specific defaults when resolving imports. */ - // "resolveJsonModule": true, /* Enable importing .json files. */ + "resolveJsonModule": true, /* Enable importing .json files. */ // "allowArbitraryExtensions": true, /* Enable importing files with any extension, provided a declaration file is present. */ // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */ diff --git a/yarn.lock b/yarn.lock index 356d7600e..1ad6c1393 100644 --- a/yarn.lock +++ b/yarn.lock @@ -976,7 +976,7 @@ "@types/range-parser" "*" "@types/send" "*" -"@types/express@^4.17.21": +"@types/express@*", "@types/express@^4.17.21": version "4.17.21" resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.21.tgz#c26d4a151e60efe0084b23dc3369ebc631ed192d" integrity sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ== @@ -1079,6 +1079,14 @@ resolved "https://registry.yarnpkg.com/@types/strip-json-comments/-/strip-json-comments-0.0.30.tgz#9aa30c04db212a9a0649d6ae6fd50accc40748a1" integrity sha512-7NQmHra/JILCd1QqpSzl8+mJRc8ZHz3uDm8YV1Ks9IhK0epEiTw8aIErbvH9PI+6XbqhyIQy3462nEsn7UVzjQ== +"@types/swagger-ui-express@^4.1.6": + version "4.1.6" + resolved "https://registry.yarnpkg.com/@types/swagger-ui-express/-/swagger-ui-express-4.1.6.tgz#d0929e3fabac1a96a8a9c6c7ee8d42362c5cdf48" + integrity sha512-UVSiGYXa5IzdJJG3hrc86e8KdZWLYxyEsVoUI4iPXc7CO4VZ3AfNP8d/8+hrDRIqz+HAaSMtZSqAsF3Nq2X/Dg== + dependencies: + "@types/express" "*" + "@types/serve-static" "*" + "@types/yargs-parser@*": version "21.0.3" resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-21.0.3.tgz#815e30b786d2e8f0dcd85fd5bcf5e1a04d008f15" @@ -3636,6 +3644,18 @@ supports-preserve-symlinks-flag@^1.0.0: resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== +swagger-ui-dist@>=5.0.0: + version "5.17.14" + resolved "https://registry.yarnpkg.com/swagger-ui-dist/-/swagger-ui-dist-5.17.14.tgz#e2c222e5bf9e15ccf80ec4bc08b4aaac09792fd6" + integrity sha512-CVbSfaLpstV65OnSjbXfVd6Sta3q3F7Cj/yYuvHMp1P90LztOLs6PfUnKEVAeiIVQt9u2SaPwv0LiH/OyMjHRw== + +swagger-ui-express@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/swagger-ui-express/-/swagger-ui-express-5.0.1.tgz#fb8c1b781d2793a6bd2f8a205a3f4bd6fa020dd8" + integrity sha512-SrNU3RiBGTLLmFU8GIJdOdanJTl4TOmT27tt3bWWHppqYmAZ6IDuEuBvMU6nZq0zLEe6b/1rACXCgLZqO6ZfrA== + dependencies: + swagger-ui-dist ">=5.0.0" + test-exclude@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/test-exclude/-/test-exclude-6.0.0.tgz#04a8698661d805ea6fa293b6cb9e63ac044ef15e" From 242d097557b97db504b0ff38e3c4647200d82e82 Mon Sep 17 00:00:00 2001 From: Leandro Reis Date: Fri, 14 Jun 2024 09:44:34 -0300 Subject: [PATCH 28/43] feat: Add ListCurrenciesController to handle listing currencies via HTTP - Created ListCurrenciesController class to handle GET requests for listing currencies. - Integrated ListCurrenciesQuery to execute the query and retrieve the currency data. - Added route registration for '/currencies' endpoint in ExpressAdapter. - Update swagger documentation --- .../query/list-currencies-query.ts | 99 +++++++++++++++++++ .../http-server/list-currencies.controller.ts | 31 ++++++ src/infra/http-server/swagger.json | 85 ++++++++++++++++ src/main/main-express-pg.ts | 4 + 4 files changed, 219 insertions(+) create mode 100644 src/application/query/list-currencies-query.ts create mode 100644 src/infra/http-server/list-currencies.controller.ts diff --git a/src/application/query/list-currencies-query.ts b/src/application/query/list-currencies-query.ts new file mode 100644 index 000000000..931e7021b --- /dev/null +++ b/src/application/query/list-currencies-query.ts @@ -0,0 +1,99 @@ +import { DatabaseConnection } from '@/infra/database'; + +export class ListCurrenciesQuery { + constructor(private readonly connection: DatabaseConnection) {} + + async execute({ + currencies, + page, + perPage: pageSize, + sortBy, + sortOrder, + }: Filters): Promise { + const rows = await this.connection.query( + ` + WITH latest_rates AS ( + SELECT + r.id, + r.currency_id, + r.rate, + r.updated_at, + ROW_NUMBER() OVER (PARTITION BY r.currency_id ORDER BY r.updated_at DESC) AS rn + FROM + rate r + ) + SELECT + c.id AS currency_id, + c.code, + c.name, + c.symbol, + c.decimal_digits, + lr.rate, + lr.updated_at, + COUNT(*) OVER() AS total_records + FROM + currency c + JOIN + latest_rates lr ON c.id = lr.currency_id + WHERE + lr.rn = 1 + AND ($1::text[] IS NULL OR c.code = ANY($1::text[])) + ORDER BY + CASE WHEN $2::text = 'name' THEN c.name END, + CASE WHEN $2::text = 'code' THEN c.code END, + CASE WHEN $2::text = 'rate' THEN lr.rate END, + $3::text + LIMIT $4 OFFSET (($5 - 1) * $4); + `, + [ + currencies?.length ? currencies : null, + sortBy, + sortOrder, + pageSize, + page, + ] + ); + const totalRecords = Number(rows[0].total_records); + return { + meta: { + page, + perPage: pageSize, + pageCount: Math.ceil(totalRecords / pageSize), + totalCount: totalRecords, + }, + data: rows.map((row: any) => ({ + id: row.currency_id, + name: row.name, + code: row.code, + symbol: row.symbol, + decimalPoints: row.decimal_digits, + rate: Number(row.rate), + })), + }; + } +} + +export type Filters = { + currencies: string[]; // BRL, USD, EUR (default all) + page: number; // 1 (default 1) + perPage: number; // 10 - 100 (default 10) + sortBy: 'name' | 'code' | 'rate'; // name (default name) + sortOrder: 'asc' | 'desc'; // asc (default asc) +}; + +export type Output = { + meta: { + page: number; + perPage: number; + pageCount: number; + totalCount: number; + }; + data: Array<{ + id: string; + name: string; + code: string; + symbol: string; + decimalPoints: number; + rate: number; + }>; +}; diff --git a/src/infra/http-server/list-currencies.controller.ts b/src/infra/http-server/list-currencies.controller.ts new file mode 100644 index 000000000..476bd0589 --- /dev/null +++ b/src/infra/http-server/list-currencies.controller.ts @@ -0,0 +1,31 @@ +import { ListCurrenciesQuery } from '@/application/query/list-currencies-query'; +import { HttpServer } from '@/infra/http-server'; + +export class ListCurrenciesController { + constructor( + readonly httpServer: HttpServer, + readonly listCurrencies: ListCurrenciesQuery + ) { + httpServer.register('get', '/currencies', async ({ query }) => { + try { + const input = { + currencies: + query.currencies?.map((currency: string) => + currency.toUpperCase() + ) || [], + page: Number(query.page) || 1, + perPage: Number(query.perPage) || 10, + sortBy: query.sortBy || 'name', + sortOrder: query.sortOrder?.toUpperCase() || 'ASC', + }; + const output = await this.listCurrencies.execute(input); + return output; + } catch (error: any) { + console.error(error.message); + throw error; + } + }); + } +} + +export default ListCurrenciesController; diff --git a/src/infra/http-server/swagger.json b/src/infra/http-server/swagger.json index 228237744..91d35772a 100644 --- a/src/infra/http-server/swagger.json +++ b/src/infra/http-server/swagger.json @@ -28,6 +28,91 @@ "basePath": "/api/v1", "paths": { "/currencies": { + "get": { + "tags": ["Currencies"], + "summary": "Lista de moedas", + "description": "Retorna a lista de moedas cadastradas", + "parameters": [ + { + "name": "currencies", + "in": "query", + "description": "Lista de moedas", + "required": false, + "schema": { + "type": "array", + "items": { "type": "string" } + } + }, + { + "name": "sortBy", + "in": "query", + "description": "Ordenação", + "required": false, + "schema": { + "type": "string", + "enum": ["name", "code", "rate"], + "default": "name" + } + }, + { + "name": "sortOrder", + "in": "query", + "description": "Ordem de ordenação", + "required": false, + "schema": { + "type": "string", + "enum": ["asc", "desc"], + "default": "asc" + } + }, + { + "name": "page", + "in": "query", + "description": "Página", + "required": false, + "schema": { "type": "number", "minimum": 1, "default": 1 } + }, + { + "name": "perPage", + "in": "query", + "description": "Número máximo de registros por página", + "required": false, + "schema": { + "type": "number", + "minimum": 10, + "maximum": 100, + "default": 10 + } + } + ], + "responses": { + "200": { + "description": "Lista paginada de moedas", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "meta": { + "type": "object", + "properties": { + "page": { "type": "number" }, + "perPage": { "type": "number" }, + "pageCount": { "type": "number" }, + "totalCount": { "type": "number" } + } + }, + "data": { + "type": "array", + "items": { "$ref": "#/components/schemas/Currency" } + } + } + } + } + } + } + } + }, "post": { "tags": ["Currencies"], "summary": "Adiciona uma nova moeda", diff --git a/src/main/main-express-pg.ts b/src/main/main-express-pg.ts index ef541389d..90fd93a69 100644 --- a/src/main/main-express-pg.ts +++ b/src/main/main-express-pg.ts @@ -1,3 +1,4 @@ +import { ListCurrenciesQuery } from '@/application/query/list-currencies-query'; import { ConvertCurrency, CreateCurrencyRate, @@ -6,6 +7,7 @@ import { } from '@/application/use-case/'; import { PgPromiseAdapter } from '@/infra/database/'; import { CurrencyController, ExpressAdapter } from '@/infra/http-server/'; +import ListCurrenciesController from '@/infra/http-server/list-currencies.controller'; import { CurrencyRateRepositoryDatabase } from '@/infra/repository/'; const httpServer = new ExpressAdapter(); @@ -24,4 +26,6 @@ new CurrencyController( getCurrencyRateByCode, deleteCurrencyRateByCode ); +const listCurrencies = new ListCurrenciesQuery(connection); +new ListCurrenciesController(httpServer, listCurrencies); httpServer.listen(3000); From 67faef13b6227815b7b7c7639ad47bff3a0afa48 Mon Sep 17 00:00:00 2001 From: Leandro Reis Date: Fri, 14 Jun 2024 12:09:31 -0300 Subject: [PATCH 29/43] refactor: split CurrencyController into multiple controllers - Refactored CurrencyController into separate controllers, each handling a specific route. - Updated routes and imports to use the new controllers. - Modified main setups to instantiate and use the new controllers. - Improved code modularity and maintainability by following the Single Responsibility Principle. --- .../query/get-currency-by-code-query.ts | 44 ++++++++++++ src/application/query/index.ts | 2 + .../query/list-currencies-query.ts | 4 +- .../convert-currency.controller.ts | 33 +++++++++ .../http-server/create-currency.controller.ts | 33 +++++++++ src/infra/http-server/currency.controller.ts | 69 ------------------- .../http-server/delete-currency.controller.ts | 21 ++++++ .../get-currency-by-code-query.controller.ts | 24 +++++++ .../get-currency-by-code.controller.ts | 22 ++++++ src/infra/http-server/index.ts | 7 +- src/main/main-express-pg.ts | 30 ++++---- src/main/main-express.ts | 23 ++++--- src/main/main-gateway.ts | 32 +++++---- src/main/main-hapi.ts | 23 ++++--- test/integration/api.test.ts | 2 +- 15 files changed, 248 insertions(+), 121 deletions(-) create mode 100644 src/application/query/get-currency-by-code-query.ts create mode 100644 src/application/query/index.ts create mode 100644 src/infra/http-server/convert-currency.controller.ts create mode 100644 src/infra/http-server/create-currency.controller.ts delete mode 100644 src/infra/http-server/currency.controller.ts create mode 100644 src/infra/http-server/delete-currency.controller.ts create mode 100644 src/infra/http-server/get-currency-by-code-query.controller.ts create mode 100644 src/infra/http-server/get-currency-by-code.controller.ts diff --git a/src/application/query/get-currency-by-code-query.ts b/src/application/query/get-currency-by-code-query.ts new file mode 100644 index 000000000..c34c0f2cd --- /dev/null +++ b/src/application/query/get-currency-by-code-query.ts @@ -0,0 +1,44 @@ +import { CurrencyRateNotFoundError } from '@/domain/errors'; +import { DatabaseConnection } from '@/infra/database'; + +export class GetCurrencyByCodeQuery { + constructor(private readonly connection: DatabaseConnection) {} + + async execute({ code }: Input): Promise { + const [row] = await this.connection.query( + ` + SELECT c.id, c.name, c.code, c.symbol, c.decimal_digits, r.rate + FROM currency c + JOIN rate r ON c.id = r.currency_id + WHERE c.code = $1 + ORDER BY r.updated_at DESC + LIMIT 1 + `, + [code] + ); + if (!row) { + throw new CurrencyRateNotFoundError(code); + } + return { + id: row.currency_id, + name: row.name, + code: row.code, + symbol: row.symbol, + decimalPoints: row.decimal_digits, + rate: Number(row.rate), + }; + } +} + +type Input = { + code: string; +}; + +type Output = { + id: string; + name: string; + code: string; + symbol: string; + decimalPoints: number; + rate: number; +}; diff --git a/src/application/query/index.ts b/src/application/query/index.ts new file mode 100644 index 000000000..b8b508a36 --- /dev/null +++ b/src/application/query/index.ts @@ -0,0 +1,2 @@ +export * from './get-currency-by-code-query'; +export * from './list-currencies-query'; diff --git a/src/application/query/list-currencies-query.ts b/src/application/query/list-currencies-query.ts index 931e7021b..c0ece2a07 100644 --- a/src/application/query/list-currencies-query.ts +++ b/src/application/query/list-currencies-query.ts @@ -73,7 +73,7 @@ export class ListCurrenciesQuery { } } -export type Filters = { +type Filters = { currencies: string[]; // BRL, USD, EUR (default all) page: number; // 1 (default 1) perPage: number; // 10 - 100 (default 10) @@ -81,7 +81,7 @@ export type Filters = { sortOrder: 'asc' | 'desc'; // asc (default asc) }; -export type Output = { +type Output = { meta: { page: number; perPage: number; diff --git a/src/infra/http-server/convert-currency.controller.ts b/src/infra/http-server/convert-currency.controller.ts new file mode 100644 index 000000000..d9371b3a0 --- /dev/null +++ b/src/infra/http-server/convert-currency.controller.ts @@ -0,0 +1,33 @@ +import { ConvertCurrency } from '@/application/use-case'; +import { HttpServer } from '@/infra/http-server'; + +export class ConvertCurrencyController { + constructor( + private readonly httpServer: HttpServer, + private readonly convertCurrency: ConvertCurrency + ) { + this.httpServer.register( + 'get', + '/currencies/convert', + async ({ query }) => { + const input = { + from: query.from.toUpperCase(), + to: query.to.toUpperCase(), + amount: Number(query.amount), + }; + const output = await this.convertCurrency.execute(input); + return { + from: input.from, + to: input.to, + givenAmount: input.amount, + convertedAmount: output.value, + decimalPoints: output.decimalPoints, + symbol: output.symbol, + convertedAmountFormatted: `${output.symbol} ${output.value.toFixed(output.decimalPoints)}`, + }; + } + ); + } +} + +export default ConvertCurrencyController; diff --git a/src/infra/http-server/create-currency.controller.ts b/src/infra/http-server/create-currency.controller.ts new file mode 100644 index 000000000..db17d1f52 --- /dev/null +++ b/src/infra/http-server/create-currency.controller.ts @@ -0,0 +1,33 @@ +import { + CreateCurrencyRate, + CreateCurrencyRateDto, +} from '@/application/use-case'; +import { HttpServer } from '@/infra/http-server'; + +export class CreateCurrencyController { + constructor( + private readonly httpServer: HttpServer, + private readonly createCurrencyRate: CreateCurrencyRate + ) { + this.httpServer.register('post', '/currencies', async ({ body }) => { + try { + const input: CreateCurrencyRateDto = { + name: body.name, + code: body.code, + symbol: body.symbol, + decimalPoints: body.decimalPoints, + baseAmount: body.baseAmount, + equivalentCurrencyAmount: body.equivalentCurrencyAmount, + equivalentCurrencyCode: body.equivalentCurrencyCode, + }; + const output = await this.createCurrencyRate.execute(input); + return output; + } catch (error: any) { + console.error(error.message); + throw error; + } + }); + } +} + +export default CreateCurrencyController; diff --git a/src/infra/http-server/currency.controller.ts b/src/infra/http-server/currency.controller.ts deleted file mode 100644 index 52dccf8a3..000000000 --- a/src/infra/http-server/currency.controller.ts +++ /dev/null @@ -1,69 +0,0 @@ -import { - ConvertCurrency, - CreateCurrencyRate, - CreateCurrencyRateDto, - DeleteCurrencyRateByCode, - GetCurrencyRateByCode, -} from '@/application/use-case'; -import { HttpServer } from '@/infra/http-server'; - -export class CurrencyController { - constructor( - readonly httpServer: HttpServer, - readonly convertCurrency: ConvertCurrency, - readonly createCurrencyRate: CreateCurrencyRate, - readonly getCurrencyRateByCode: GetCurrencyRateByCode, - readonly deleteCurrencyRateByCode: DeleteCurrencyRateByCode - ) { - httpServer.register('post', '/currencies', async ({ body }) => { - try { - const input: CreateCurrencyRateDto = { - name: body.name, - code: body.code, - symbol: body.symbol, - decimalPoints: body.decimalPoints, - baseAmount: body.baseAmount, - equivalentCurrencyAmount: body.equivalentCurrencyAmount, - equivalentCurrencyCode: body.equivalentCurrencyCode, - }; - const output = await this.createCurrencyRate.execute(input); - return output; - } catch (error: any) { - console.error(error.message); - throw error; - } - }); - - httpServer.register('get', '/currencies/convert', async ({ query }) => { - const input = { - from: query.from.toUpperCase(), - to: query.to.toUpperCase(), - amount: Number(query.amount), - }; - const output = await this.convertCurrency.execute(input); - return { - from: input.from, - to: input.to, - givenAmount: input.amount, - convertedAmount: output.value, - decimalPoints: output.decimalPoints, - symbol: output.symbol, - convertedAmountFormatted: `${output.symbol} ${output.value.toFixed(output.decimalPoints)}`, - }; - }); - - httpServer.register('get', '/currencies/{code}', async ({ params }) => { - const { code } = params; - const output = await this.getCurrencyRateByCode.execute(code); - return output; - }); - - httpServer.register('delete', '/currencies/{code}', async ({ params }) => { - const { code } = params; - await this.deleteCurrencyRateByCode.execute(code); - return { message: 'Currency deleted' }; - }); - } -} - -export default CurrencyController; diff --git a/src/infra/http-server/delete-currency.controller.ts b/src/infra/http-server/delete-currency.controller.ts new file mode 100644 index 000000000..4aefdad39 --- /dev/null +++ b/src/infra/http-server/delete-currency.controller.ts @@ -0,0 +1,21 @@ +import { DeleteCurrencyRateByCode } from '@/application/use-case'; +import { HttpServer } from '@/infra/http-server'; + +export class DeleteCurrencyController { + constructor( + private readonly httpServer: HttpServer, + private readonly deleteCurrencyRateByCode: DeleteCurrencyRateByCode + ) { + this.httpServer.register( + 'delete', + '/currencies/{code}', + async ({ params }) => { + const { code } = params; + await this.deleteCurrencyRateByCode.execute(code); + return { message: 'Currency deleted' }; + } + ); + } +} + +export default DeleteCurrencyController; diff --git a/src/infra/http-server/get-currency-by-code-query.controller.ts b/src/infra/http-server/get-currency-by-code-query.controller.ts new file mode 100644 index 000000000..f14055fd9 --- /dev/null +++ b/src/infra/http-server/get-currency-by-code-query.controller.ts @@ -0,0 +1,24 @@ +import { GetCurrencyByCodeQuery } from '@/application/query/'; +import { HttpServer } from '@/infra/http-server'; + +export class GetCurrencyByCodeQueryController { + constructor( + readonly httpServer: HttpServer, + readonly getCurrencyByCode: GetCurrencyByCodeQuery + ) { + httpServer.register('get', '/currencies/{code}', async ({ query }) => { + try { + const input = { + code: query.code?.toUpperCase(), + }; + const output = await this.getCurrencyByCode.execute(input); + return output; + } catch (error: any) { + console.error(error.message); + throw error; + } + }); + } +} + +export default GetCurrencyByCodeQueryController; diff --git a/src/infra/http-server/get-currency-by-code.controller.ts b/src/infra/http-server/get-currency-by-code.controller.ts new file mode 100644 index 000000000..7b564379d --- /dev/null +++ b/src/infra/http-server/get-currency-by-code.controller.ts @@ -0,0 +1,22 @@ +import { GetCurrencyRateByCode } from '@/application/use-case'; +import { HttpServer } from '@/infra/http-server'; + +export class GetCurrencyByCodeController { + constructor( + readonly httpServer: HttpServer, + readonly getCurrencyByCode: GetCurrencyRateByCode + ) { + httpServer.register('get', '/currencies/{code}', async ({ params }) => { + try { + const input = params.code?.toUpperCase(); + const output = await this.getCurrencyByCode.execute(input); + return output; + } catch (error: any) { + console.error(error.message); + throw error; + } + }); + } +} + +export default GetCurrencyByCodeController; diff --git a/src/infra/http-server/index.ts b/src/infra/http-server/index.ts index 92fc21e8b..be0fbaac7 100644 --- a/src/infra/http-server/index.ts +++ b/src/infra/http-server/index.ts @@ -1,4 +1,9 @@ -export * from './currency.controller'; +export * from './convert-currency.controller'; +export * from './create-currency.controller'; +export * from './delete-currency.controller'; export * from './express-adapter'; +export * from './get-currency-by-code-query.controller'; +export * from './get-currency-by-code.controller'; export * from './hapi-adapter'; export * from './http-server'; +export * from './list-currencies.controller'; diff --git a/src/main/main-express-pg.ts b/src/main/main-express-pg.ts index 90fd93a69..6e571fb47 100644 --- a/src/main/main-express-pg.ts +++ b/src/main/main-express-pg.ts @@ -1,31 +1,35 @@ +import { GetCurrencyByCodeQuery } from '@/application/query'; import { ListCurrenciesQuery } from '@/application/query/list-currencies-query'; import { ConvertCurrency, CreateCurrencyRate, DeleteCurrencyRateByCode, - GetCurrencyRateByCode, } from '@/application/use-case/'; import { PgPromiseAdapter } from '@/infra/database/'; -import { CurrencyController, ExpressAdapter } from '@/infra/http-server/'; -import ListCurrenciesController from '@/infra/http-server/list-currencies.controller'; +import { + ConvertCurrencyController, + CreateCurrencyController, + DeleteCurrencyController, + ExpressAdapter, + GetCurrencyByCodeQueryController, + ListCurrenciesController, +} from '@/infra/http-server/'; import { CurrencyRateRepositoryDatabase } from '@/infra/repository/'; const httpServer = new ExpressAdapter(); const connection = new PgPromiseAdapter(); +const listCurrencies = new ListCurrenciesQuery(connection); const currencyRateRepository = new CurrencyRateRepositoryDatabase(connection); -const currencyConverter = new ConvertCurrency(currencyRateRepository); const createCurrencyRate = new CreateCurrencyRate(currencyRateRepository); -const getCurrencyRateByCode = new GetCurrencyRateByCode(currencyRateRepository); +const convertCurrency = new ConvertCurrency(currencyRateRepository); +// const getCurrencyRateByCode = new GetCurrencyRateByCode(currencyRateRepository); +const getCurrencyByCodeQuery = new GetCurrencyByCodeQuery(connection); const deleteCurrencyRateByCode = new DeleteCurrencyRateByCode( currencyRateRepository ); -new CurrencyController( - httpServer, - currencyConverter, - createCurrencyRate, - getCurrencyRateByCode, - deleteCurrencyRateByCode -); -const listCurrencies = new ListCurrenciesQuery(connection); new ListCurrenciesController(httpServer, listCurrencies); +new CreateCurrencyController(httpServer, createCurrencyRate); +new ConvertCurrencyController(httpServer, convertCurrency); +new GetCurrencyByCodeQueryController(httpServer, getCurrencyByCodeQuery); +new DeleteCurrencyController(httpServer, deleteCurrencyRateByCode); httpServer.listen(3000); diff --git a/src/main/main-express.ts b/src/main/main-express.ts index d20d30046..f57fd33bc 100644 --- a/src/main/main-express.ts +++ b/src/main/main-express.ts @@ -4,22 +4,25 @@ import { DeleteCurrencyRateByCode, GetCurrencyRateByCode, } from '@/application/use-case/'; -import { CurrencyController, ExpressAdapter } from '@/infra/http-server/'; +import { + ConvertCurrencyController, + CreateCurrencyController, + DeleteCurrencyController, + ExpressAdapter, + GetCurrencyByCodeController, +} from '@/infra/http-server/'; import { CurrencyRateRepositoryFake } from '@/infra/repository/'; const httpServer = new ExpressAdapter(); const currencyRateRepository = new CurrencyRateRepositoryFake(); -const currencyConverter = new ConvertCurrency(currencyRateRepository); const createCurrencyRate = new CreateCurrencyRate(currencyRateRepository); -const getCurrencyRateByCode = new GetCurrencyRateByCode(currencyRateRepository); +const convertCurrency = new ConvertCurrency(currencyRateRepository); +const getCurrencyByCode = new GetCurrencyRateByCode(currencyRateRepository); const deleteCurrencyRateByCode = new DeleteCurrencyRateByCode( currencyRateRepository ); -new CurrencyController( - httpServer, - currencyConverter, - createCurrencyRate, - getCurrencyRateByCode, - deleteCurrencyRateByCode -); +new CreateCurrencyController(httpServer, createCurrencyRate); +new ConvertCurrencyController(httpServer, convertCurrency); +new GetCurrencyByCodeController(httpServer, getCurrencyByCode); +new DeleteCurrencyController(httpServer, deleteCurrencyRateByCode); httpServer.listen(3000); diff --git a/src/main/main-gateway.ts b/src/main/main-gateway.ts index 020e12017..1483cc1ca 100644 --- a/src/main/main-gateway.ts +++ b/src/main/main-gateway.ts @@ -4,27 +4,29 @@ import { DeleteCurrencyRateByCode, GetCurrencyRateByCode, } from '@/application/use-case/'; -import { CurrenciesApiGatewayOnline } from '@/infra/gateway'; -import { AxiosAdapter } from '@/infra/http-client'; -import { CurrencyController, ExpressAdapter } from '@/infra/http-server/'; +// import { CurrenciesApiGatewayOnline } from '@/infra/gateway'; +import { CurrenciesApiGatewayStatic } from '@/infra/gateway'; +import { + ConvertCurrencyController, + CreateCurrencyController, + DeleteCurrencyController, + ExpressAdapter, + GetCurrencyByCodeController, +} from '@/infra/http-server/'; import { CurrencyRateRepositoryApi } from '@/infra/repository/'; const httpServer = new ExpressAdapter(); -// const currencyGateway = new CurrenciesApiGatewayStatic() -const httpClient = new AxiosAdapter(); -const currencyGateway = new CurrenciesApiGatewayOnline(httpClient); +// const httpClient = new AxiosAdapter(); +const currencyGateway = new CurrenciesApiGatewayStatic(); const currencyRateRepository = new CurrencyRateRepositoryApi(currencyGateway); -const currencyConverter = new ConvertCurrency(currencyRateRepository); const createCurrencyRate = new CreateCurrencyRate(currencyRateRepository); -const getCurrencyRateByCode = new GetCurrencyRateByCode(currencyRateRepository); +const convertCurrency = new ConvertCurrency(currencyRateRepository); +const getCurrencyByCode = new GetCurrencyRateByCode(currencyRateRepository); const deleteCurrencyRateByCode = new DeleteCurrencyRateByCode( currencyRateRepository ); -new CurrencyController( - httpServer, - currencyConverter, - createCurrencyRate, - getCurrencyRateByCode, - deleteCurrencyRateByCode -); +new CreateCurrencyController(httpServer, createCurrencyRate); +new ConvertCurrencyController(httpServer, convertCurrency); +new GetCurrencyByCodeController(httpServer, getCurrencyByCode); +new DeleteCurrencyController(httpServer, deleteCurrencyRateByCode); httpServer.listen(3000); diff --git a/src/main/main-hapi.ts b/src/main/main-hapi.ts index b196a4edb..afa97d592 100644 --- a/src/main/main-hapi.ts +++ b/src/main/main-hapi.ts @@ -4,22 +4,25 @@ import { DeleteCurrencyRateByCode, GetCurrencyRateByCode, } from '@/application/use-case/'; -import { CurrencyController, HapiAdapter } from '@/infra/http-server/'; +import { + ConvertCurrencyController, + CreateCurrencyController, + DeleteCurrencyController, + GetCurrencyByCodeController, + HapiAdapter, +} from '@/infra/http-server/'; import { CurrencyRateRepositoryFake } from '@/infra/repository/'; const httpServer = new HapiAdapter(); const currencyRateRepository = new CurrencyRateRepositoryFake(); -const currencyConverter = new ConvertCurrency(currencyRateRepository); const createCurrencyRate = new CreateCurrencyRate(currencyRateRepository); -const getCurrencyRateByCode = new GetCurrencyRateByCode(currencyRateRepository); +const convertCurrency = new ConvertCurrency(currencyRateRepository); +const getCurrencyByCode = new GetCurrencyRateByCode(currencyRateRepository); const deleteCurrencyRateByCode = new DeleteCurrencyRateByCode( currencyRateRepository ); -new CurrencyController( - httpServer, - currencyConverter, - createCurrencyRate, - getCurrencyRateByCode, - deleteCurrencyRateByCode -); +new CreateCurrencyController(httpServer, createCurrencyRate); +new ConvertCurrencyController(httpServer, convertCurrency); +new GetCurrencyByCodeController(httpServer, getCurrencyByCode); +new DeleteCurrencyController(httpServer, deleteCurrencyRateByCode); httpServer.listen(3000); diff --git a/test/integration/api.test.ts b/test/integration/api.test.ts index 93d7e9409..f2aded80d 100644 --- a/test/integration/api.test.ts +++ b/test/integration/api.test.ts @@ -39,7 +39,7 @@ describe('Main API', () => { it('should create, get and delete a currency rate', async () => { const payload = { name: 'Any Currency', - code: 'ANYC', + code: 'ANY', symbol: '$', decimalPoints: 2, baseAmount: 2, From 5bd7ff4e4a08414e72e3b046c36c00707769e176 Mon Sep 17 00:00:00 2001 From: Leandro Reis Date: Fri, 14 Jun 2024 15:23:27 -0300 Subject: [PATCH 30/43] refactor: extract route responsibility from controllers - Removed route handling responsibility from controllers to improve separation of concerns. - Updated the following controllers to focus only on handling their specific logic: - ConvertCurrencyController - CreateCurrencyController - DeleteCurrencyController - GetCurrencyByCodeController - ListCurrenciesController - Updated the main application setup files to register routes separately from the controller logic. - Modified `main-express-pg.ts`, `main-express.ts`, `main-gateway.ts`, and `main-hapi.ts` to use the new routing structure. Affected files: - src/application/query/* - src/application/use-case/* - src/infra/http-server/* - src/main/* --- src/infra/http-server/controller.ts | 17 ++++++ .../convert-currency.controller.ts | 50 ++++++++--------- .../http-server/create-currency.controller.ts | 53 +++++++++---------- .../http-server/delete-currency.controller.ts | 31 ++++++----- src/infra/http-server/express-adapter.ts | 16 +++--- .../get-currency-by-code-query.controller.ts | 36 ++++++------- .../get-currency-by-code.controller.ts | 32 +++++------ src/infra/http-server/hapi-adapter.ts | 18 ++++--- src/infra/http-server/http-server.ts | 14 ++--- .../http-server/list-currencies.controller.ts | 49 +++++++++-------- src/main/main-express-pg.ts | 23 ++++++-- src/main/main-express.ts | 20 +++++-- src/main/main-gateway.ts | 20 +++++-- src/main/main-hapi.ts | 20 +++++-- 14 files changed, 232 insertions(+), 167 deletions(-) create mode 100644 src/infra/http-server/controller.ts diff --git a/src/infra/http-server/controller.ts b/src/infra/http-server/controller.ts new file mode 100644 index 000000000..187331817 --- /dev/null +++ b/src/infra/http-server/controller.ts @@ -0,0 +1,17 @@ +export interface Controller< + TResponse extends Response = Response, + TRequest extends Request = Request, +> { + handle: (request: TRequest) => Promise; +} + +export type Request = { + query: { [key: string]: any }; + body: any; + params: { [key: string]: string }; +}; + +export type Response = { + statusCode: number; + data: T; +}; diff --git a/src/infra/http-server/convert-currency.controller.ts b/src/infra/http-server/convert-currency.controller.ts index d9371b3a0..eb1dfd1da 100644 --- a/src/infra/http-server/convert-currency.controller.ts +++ b/src/infra/http-server/convert-currency.controller.ts @@ -1,32 +1,28 @@ import { ConvertCurrency } from '@/application/use-case'; -import { HttpServer } from '@/infra/http-server'; +import { Controller, Request, Response } from '@/infra/http-server/controller'; -export class ConvertCurrencyController { - constructor( - private readonly httpServer: HttpServer, - private readonly convertCurrency: ConvertCurrency - ) { - this.httpServer.register( - 'get', - '/currencies/convert', - async ({ query }) => { - const input = { - from: query.from.toUpperCase(), - to: query.to.toUpperCase(), - amount: Number(query.amount), - }; - const output = await this.convertCurrency.execute(input); - return { - from: input.from, - to: input.to, - givenAmount: input.amount, - convertedAmount: output.value, - decimalPoints: output.decimalPoints, - symbol: output.symbol, - convertedAmountFormatted: `${output.symbol} ${output.value.toFixed(output.decimalPoints)}`, - }; - } - ); +export class ConvertCurrencyController implements Controller { + constructor(private readonly convertCurrency: ConvertCurrency) {} + + async handle({ query }: Request): Promise { + const input = { + from: query.from.toUpperCase(), + to: query.to.toUpperCase(), + amount: Number(query.amount), + }; + const output = await this.convertCurrency.execute(input); + return { + statusCode: 200, + data: { + from: input.from, + to: input.to, + givenAmount: input.amount, + convertedAmount: output.value, + decimalPoints: output.decimalPoints, + symbol: output.symbol, + convertedAmountFormatted: `${output.symbol} ${output.value.toFixed(output.decimalPoints)}`, + }, + }; } } diff --git a/src/infra/http-server/create-currency.controller.ts b/src/infra/http-server/create-currency.controller.ts index db17d1f52..065741a8f 100644 --- a/src/infra/http-server/create-currency.controller.ts +++ b/src/infra/http-server/create-currency.controller.ts @@ -1,32 +1,29 @@ -import { - CreateCurrencyRate, - CreateCurrencyRateDto, -} from '@/application/use-case'; -import { HttpServer } from '@/infra/http-server'; +import { CreateCurrencyRate } from '@/application/use-case'; +import { Controller, Request, Response } from '@/infra/http-server/controller'; -export class CreateCurrencyController { - constructor( - private readonly httpServer: HttpServer, - private readonly createCurrencyRate: CreateCurrencyRate - ) { - this.httpServer.register('post', '/currencies', async ({ body }) => { - try { - const input: CreateCurrencyRateDto = { - name: body.name, - code: body.code, - symbol: body.symbol, - decimalPoints: body.decimalPoints, - baseAmount: body.baseAmount, - equivalentCurrencyAmount: body.equivalentCurrencyAmount, - equivalentCurrencyCode: body.equivalentCurrencyCode, - }; - const output = await this.createCurrencyRate.execute(input); - return output; - } catch (error: any) { - console.error(error.message); - throw error; - } - }); +export class CreateCurrencyController implements Controller { + constructor(private readonly createCurrencyRate: CreateCurrencyRate) {} + + async handle({ body }: Request): Promise { + try { + const input = { + name: body.name, + code: body.code, + symbol: body.symbol, + decimalPoints: body.decimalPoints, + baseAmount: body.baseAmount, + equivalentCurrencyAmount: body.equivalentCurrencyAmount, + equivalentCurrencyCode: body.equivalentCurrencyCode, + }; + const output = await this.createCurrencyRate.execute(input); + return { + statusCode: 200, + data: output, + }; + } catch (error: any) { + console.error(error.message); + throw error; + } } } diff --git a/src/infra/http-server/delete-currency.controller.ts b/src/infra/http-server/delete-currency.controller.ts index 4aefdad39..3e7a89183 100644 --- a/src/infra/http-server/delete-currency.controller.ts +++ b/src/infra/http-server/delete-currency.controller.ts @@ -1,20 +1,25 @@ import { DeleteCurrencyRateByCode } from '@/application/use-case'; -import { HttpServer } from '@/infra/http-server'; +import { Controller, Request, Response } from '@/infra/http-server/controller'; -export class DeleteCurrencyController { +export class DeleteCurrencyController implements Controller { constructor( - private readonly httpServer: HttpServer, private readonly deleteCurrencyRateByCode: DeleteCurrencyRateByCode - ) { - this.httpServer.register( - 'delete', - '/currencies/{code}', - async ({ params }) => { - const { code } = params; - await this.deleteCurrencyRateByCode.execute(code); - return { message: 'Currency deleted' }; - } - ); + ) {} + + async handle({ params }: Request): Promise { + try { + const code = params?.code.toUpperCase(); + await this.deleteCurrencyRateByCode.execute(code); + return { + statusCode: 200, + data: { + message: 'Currency deleted', + }, + }; + } catch (error: any) { + console.error(error.message); + throw error; + } } } diff --git a/src/infra/http-server/express-adapter.ts b/src/infra/http-server/express-adapter.ts index 1577d0637..15deeeae5 100644 --- a/src/infra/http-server/express-adapter.ts +++ b/src/infra/http-server/express-adapter.ts @@ -1,4 +1,5 @@ -import { Controller, HttpServer, Method } from '@/infra/http-server'; +import { HttpServer, Method } from '@/infra/http-server'; +import { Controller, Request } from '@/infra/http-server/controller'; import swaggerDocument from '@/infra/http-server/swagger.json'; import SwaggerUi from 'swagger-ui-express'; @@ -17,19 +18,22 @@ export class ExpressAdapter implements HttpServer { ); } - register(method: Method, route: string, handler: Controller): void { + register(method: Method, route: string, controller: Controller): void { this.app[method]( route.replace('}', '').replace('{', ':'), async (req, res) => { try { - const result = await handler({ + const request: Request = { query: req.query, params: req.params, body: req.body, - }); - res.json(result); + }; + const response = await controller.handle(request); + res.status(response.statusCode).json(response.data); } catch (error: any) { - res.status(500).json({ message: error.message }); + res + .status(500) + .json({ message: error.message || 'An unexpected error occurred' }); } } ); diff --git a/src/infra/http-server/get-currency-by-code-query.controller.ts b/src/infra/http-server/get-currency-by-code-query.controller.ts index f14055fd9..26197b523 100644 --- a/src/infra/http-server/get-currency-by-code-query.controller.ts +++ b/src/infra/http-server/get-currency-by-code-query.controller.ts @@ -1,23 +1,23 @@ import { GetCurrencyByCodeQuery } from '@/application/query/'; -import { HttpServer } from '@/infra/http-server'; +import { Controller, Request, Response } from '@/infra/http-server/controller'; -export class GetCurrencyByCodeQueryController { - constructor( - readonly httpServer: HttpServer, - readonly getCurrencyByCode: GetCurrencyByCodeQuery - ) { - httpServer.register('get', '/currencies/{code}', async ({ query }) => { - try { - const input = { - code: query.code?.toUpperCase(), - }; - const output = await this.getCurrencyByCode.execute(input); - return output; - } catch (error: any) { - console.error(error.message); - throw error; - } - }); +export class GetCurrencyByCodeQueryController implements Controller { + constructor(private readonly getCurrencyByCode: GetCurrencyByCodeQuery) {} + + async handle({ params }: Request): Promise { + try { + const input = { + code: params.code?.toUpperCase(), + }; + const output = await this.getCurrencyByCode.execute(input); + return { + statusCode: 200, + data: output, + }; + } catch (error: any) { + console.error(error.message); + throw error; + } } } diff --git a/src/infra/http-server/get-currency-by-code.controller.ts b/src/infra/http-server/get-currency-by-code.controller.ts index 7b564379d..19cde9e52 100644 --- a/src/infra/http-server/get-currency-by-code.controller.ts +++ b/src/infra/http-server/get-currency-by-code.controller.ts @@ -1,21 +1,21 @@ import { GetCurrencyRateByCode } from '@/application/use-case'; -import { HttpServer } from '@/infra/http-server'; +import { Controller, Request, Response } from '@/infra/http-server/controller'; -export class GetCurrencyByCodeController { - constructor( - readonly httpServer: HttpServer, - readonly getCurrencyByCode: GetCurrencyRateByCode - ) { - httpServer.register('get', '/currencies/{code}', async ({ params }) => { - try { - const input = params.code?.toUpperCase(); - const output = await this.getCurrencyByCode.execute(input); - return output; - } catch (error: any) { - console.error(error.message); - throw error; - } - }); +export class GetCurrencyByCodeController implements Controller { + constructor(readonly getCurrencyByCode: GetCurrencyRateByCode) {} + + async handle({ params }: Request): Promise { + try { + const input = params.code?.toUpperCase(); + const output = await this.getCurrencyByCode.execute(input); + return { + statusCode: 200, + data: output, + }; + } catch (error: any) { + console.error(error.message); + throw error; + } } } diff --git a/src/infra/http-server/hapi-adapter.ts b/src/infra/http-server/hapi-adapter.ts index e7c93df86..eae369b0e 100644 --- a/src/infra/http-server/hapi-adapter.ts +++ b/src/infra/http-server/hapi-adapter.ts @@ -1,4 +1,5 @@ -import { Controller, HttpServer, Method } from '@/infra/http-server'; +import { HttpServer, Method } from '@/infra/http-server'; +import { Controller, Request } from '@/infra/http-server/controller'; import Hapi from '@hapi/hapi'; @@ -9,20 +10,25 @@ export class HapiAdapter implements HttpServer { this.server = Hapi.server({}); } - register(method: Method, route: string, handler: Controller): void { + register(method: Method, route: string, controller: Controller): void { this.server.route({ method, path: route, handler: async (req, res) => { try { - const result = await handler({ + const request: Request = { query: req.query, params: req.params, body: req.payload, - }); - return result; + }; + const result = await controller.handle(request); + return res.response(result.data).code(result.statusCode); } catch (error: any) { - return res.response({ message: error.message }).code(500); + return res + .response({ + message: error.message || 'An unexpected error occurred', + }) + .code(500); } }, }); diff --git a/src/infra/http-server/http-server.ts b/src/infra/http-server/http-server.ts index 48a80298b..6a9ef0a26 100644 --- a/src/infra/http-server/http-server.ts +++ b/src/infra/http-server/http-server.ts @@ -1,16 +1,8 @@ +import { Controller } from './controller'; + export interface HttpServer { - register(method: Method, route: string, handler: Controller): void; + register(method: Method, route: string, handler: Controller): void; listen(port: number): void; } export type Method = 'get' | 'post' | 'put' | 'delete'; - -export type Controller = ( - args: ControllerParams -) => Promise; - -export type ControllerParams = { - query: { [key: string]: any }; - params: { [key: string]: string }; - body: T; -}; diff --git a/src/infra/http-server/list-currencies.controller.ts b/src/infra/http-server/list-currencies.controller.ts index 476bd0589..584d8299a 100644 --- a/src/infra/http-server/list-currencies.controller.ts +++ b/src/infra/http-server/list-currencies.controller.ts @@ -1,30 +1,29 @@ import { ListCurrenciesQuery } from '@/application/query/list-currencies-query'; -import { HttpServer } from '@/infra/http-server'; +import { Controller, Request, Response } from '@/infra/http-server/controller'; -export class ListCurrenciesController { - constructor( - readonly httpServer: HttpServer, - readonly listCurrencies: ListCurrenciesQuery - ) { - httpServer.register('get', '/currencies', async ({ query }) => { - try { - const input = { - currencies: - query.currencies?.map((currency: string) => - currency.toUpperCase() - ) || [], - page: Number(query.page) || 1, - perPage: Number(query.perPage) || 10, - sortBy: query.sortBy || 'name', - sortOrder: query.sortOrder?.toUpperCase() || 'ASC', - }; - const output = await this.listCurrencies.execute(input); - return output; - } catch (error: any) { - console.error(error.message); - throw error; - } - }); +export class ListCurrenciesController implements Controller { + constructor(private readonly listCurrencies: ListCurrenciesQuery) {} + + async handle({ query }: Request): Promise { + try { + const input = { + currencies: + query.currencies?.map((currency: string) => currency.toUpperCase()) || + [], + page: Number(query.page) || 1, + perPage: Number(query.perPage) || 10, + sortBy: query.sortBy || 'name', + sortOrder: query.sortOrder?.toUpperCase() || 'ASC', + }; + const output = await this.listCurrencies.execute(input); + return { + statusCode: 200, + data: output, + }; + } catch (error: any) { + console.error(error.message); + throw error; + } } } diff --git a/src/main/main-express-pg.ts b/src/main/main-express-pg.ts index 6e571fb47..5ed326a07 100644 --- a/src/main/main-express-pg.ts +++ b/src/main/main-express-pg.ts @@ -27,9 +27,22 @@ const getCurrencyByCodeQuery = new GetCurrencyByCodeQuery(connection); const deleteCurrencyRateByCode = new DeleteCurrencyRateByCode( currencyRateRepository ); -new ListCurrenciesController(httpServer, listCurrencies); -new CreateCurrencyController(httpServer, createCurrencyRate); -new ConvertCurrencyController(httpServer, convertCurrency); -new GetCurrencyByCodeQueryController(httpServer, getCurrencyByCodeQuery); -new DeleteCurrencyController(httpServer, deleteCurrencyRateByCode); +const listCurrenciesController = new ListCurrenciesController(listCurrencies); +const createCurrencyController = new CreateCurrencyController( + createCurrencyRate +); +const convertCurrencyController = new ConvertCurrencyController( + convertCurrency +); +const getCurrencyByCodeController = new GetCurrencyByCodeQueryController( + getCurrencyByCodeQuery +); +const deleteCurrencyController = new DeleteCurrencyController( + deleteCurrencyRateByCode +); +httpServer.register('get', '/currencies', listCurrenciesController); +httpServer.register('post', '/currencies', createCurrencyController); +httpServer.register('get', '/currencies/convert', convertCurrencyController); +httpServer.register('get', '/currencies/{code}', getCurrencyByCodeController); +httpServer.register('delete', '/currencies/{code}', deleteCurrencyController); httpServer.listen(3000); diff --git a/src/main/main-express.ts b/src/main/main-express.ts index f57fd33bc..63c0b55ba 100644 --- a/src/main/main-express.ts +++ b/src/main/main-express.ts @@ -21,8 +21,20 @@ const getCurrencyByCode = new GetCurrencyRateByCode(currencyRateRepository); const deleteCurrencyRateByCode = new DeleteCurrencyRateByCode( currencyRateRepository ); -new CreateCurrencyController(httpServer, createCurrencyRate); -new ConvertCurrencyController(httpServer, convertCurrency); -new GetCurrencyByCodeController(httpServer, getCurrencyByCode); -new DeleteCurrencyController(httpServer, deleteCurrencyRateByCode); +const createCurrencyController = new CreateCurrencyController( + createCurrencyRate +); +const convertCurrencyController = new ConvertCurrencyController( + convertCurrency +); +const getCurrencyByCodeController = new GetCurrencyByCodeController( + getCurrencyByCode +); +const deleteCurrencyController = new DeleteCurrencyController( + deleteCurrencyRateByCode +); +httpServer.register('post', '/currencies', createCurrencyController); +httpServer.register('get', '/currencies/convert', convertCurrencyController); +httpServer.register('get', '/currencies/{code}', getCurrencyByCodeController); +httpServer.register('delete', '/currencies/{code}', deleteCurrencyController); httpServer.listen(3000); diff --git a/src/main/main-gateway.ts b/src/main/main-gateway.ts index 1483cc1ca..974f08cb0 100644 --- a/src/main/main-gateway.ts +++ b/src/main/main-gateway.ts @@ -25,8 +25,20 @@ const getCurrencyByCode = new GetCurrencyRateByCode(currencyRateRepository); const deleteCurrencyRateByCode = new DeleteCurrencyRateByCode( currencyRateRepository ); -new CreateCurrencyController(httpServer, createCurrencyRate); -new ConvertCurrencyController(httpServer, convertCurrency); -new GetCurrencyByCodeController(httpServer, getCurrencyByCode); -new DeleteCurrencyController(httpServer, deleteCurrencyRateByCode); +const createCurrencyController = new CreateCurrencyController( + createCurrencyRate +); +const convertCurrencyController = new ConvertCurrencyController( + convertCurrency +); +const getCurrencyByCodeController = new GetCurrencyByCodeController( + getCurrencyByCode +); +const deleteCurrencyController = new DeleteCurrencyController( + deleteCurrencyRateByCode +); +httpServer.register('post', '/currencies', createCurrencyController); +httpServer.register('get', '/currencies/convert', convertCurrencyController); +httpServer.register('get', '/currencies/{code}', getCurrencyByCodeController); +httpServer.register('delete', '/currencies/{code}', deleteCurrencyController); httpServer.listen(3000); diff --git a/src/main/main-hapi.ts b/src/main/main-hapi.ts index afa97d592..ae8c244b1 100644 --- a/src/main/main-hapi.ts +++ b/src/main/main-hapi.ts @@ -21,8 +21,20 @@ const getCurrencyByCode = new GetCurrencyRateByCode(currencyRateRepository); const deleteCurrencyRateByCode = new DeleteCurrencyRateByCode( currencyRateRepository ); -new CreateCurrencyController(httpServer, createCurrencyRate); -new ConvertCurrencyController(httpServer, convertCurrency); -new GetCurrencyByCodeController(httpServer, getCurrencyByCode); -new DeleteCurrencyController(httpServer, deleteCurrencyRateByCode); +const createCurrencyController = new CreateCurrencyController( + createCurrencyRate +); +const convertCurrencyController = new ConvertCurrencyController( + convertCurrency +); +const getCurrencyByCodeController = new GetCurrencyByCodeController( + getCurrencyByCode +); +const deleteCurrencyController = new DeleteCurrencyController( + deleteCurrencyRateByCode +); +httpServer.register('post', '/currencies', createCurrencyController); +httpServer.register('get', '/currencies/convert', convertCurrencyController); +httpServer.register('get', '/currencies/{code}', getCurrencyByCodeController); +httpServer.register('delete', '/currencies/{code}', deleteCurrencyController); httpServer.listen(3000); From 64c02ca1309ae61a6d36aa13e7328bd59c0b7462 Mon Sep 17 00:00:00 2001 From: Leandro Reis Date: Fri, 14 Jun 2024 16:23:54 -0300 Subject: [PATCH 31/43] refactor: extract route definitions into CurrenciesRoutes class - Created a new CurrenciesRoutes class to handle the registration of routes. - Removed route handling responsibility from the main files to improve separation of concerns. - Updated the following files to utilize the new CurrenciesRoutes class: - main-express-pg.ts - main-express.ts - main-gateway.ts - main-hapi.ts --- src/infra/http-server/currencies.routes.ts | 48 ++++++++++++++++++++++ src/infra/http-server/index.ts | 1 + src/main/main-express-pg.ts | 16 +++++--- src/main/main-express.ts | 12 ++++-- src/main/main-gateway.ts | 12 ++++-- src/main/main-hapi.ts | 12 ++++-- 6 files changed, 83 insertions(+), 18 deletions(-) create mode 100644 src/infra/http-server/currencies.routes.ts diff --git a/src/infra/http-server/currencies.routes.ts b/src/infra/http-server/currencies.routes.ts new file mode 100644 index 000000000..557b455fd --- /dev/null +++ b/src/infra/http-server/currencies.routes.ts @@ -0,0 +1,48 @@ +import { Controller } from './controller'; +import { HttpServer } from './http-server'; + +export class CurrenciesRoutes { + private readonly listCurrenciesController?: Controller; + private readonly createCurrencyController: Controller; + private readonly convertCurrencyController: Controller; + private readonly getCurrencyByCodeController: Controller; + private readonly deleteCurrencyController: Controller; + + constructor(dependencies: Controllers) { + this.listCurrenciesController = dependencies.listCurrenciesController; + this.createCurrencyController = dependencies.createCurrencyController; + this.convertCurrencyController = dependencies.convertCurrencyController; + this.getCurrencyByCodeController = dependencies.getCurrencyByCodeController; + this.deleteCurrencyController = dependencies.deleteCurrencyController; + } + + registerRoutes(httpServer: HttpServer) { + if (this.listCurrenciesController) { + httpServer.register('get', '/currencies', this.listCurrenciesController); + } + httpServer.register('post', '/currencies', this.createCurrencyController); + httpServer.register( + 'get', + '/currencies/convert', + this.convertCurrencyController + ); + httpServer.register( + 'get', + '/currencies/{code}', + this.getCurrencyByCodeController + ); + httpServer.register( + 'delete', + '/currencies/{code}', + this.deleteCurrencyController + ); + } +} + +type Controllers = { + listCurrenciesController?: Controller; + createCurrencyController: Controller; + convertCurrencyController: Controller; + getCurrencyByCodeController: Controller; + deleteCurrencyController: Controller; +}; diff --git a/src/infra/http-server/index.ts b/src/infra/http-server/index.ts index be0fbaac7..f5d68fd78 100644 --- a/src/infra/http-server/index.ts +++ b/src/infra/http-server/index.ts @@ -1,5 +1,6 @@ export * from './convert-currency.controller'; export * from './create-currency.controller'; +export * from './currencies.routes'; export * from './delete-currency.controller'; export * from './express-adapter'; export * from './get-currency-by-code-query.controller'; diff --git a/src/main/main-express-pg.ts b/src/main/main-express-pg.ts index 5ed326a07..1814651d8 100644 --- a/src/main/main-express-pg.ts +++ b/src/main/main-express-pg.ts @@ -1,5 +1,5 @@ import { GetCurrencyByCodeQuery } from '@/application/query'; -import { ListCurrenciesQuery } from '@/application/query/list-currencies-query'; +import { ListCurrenciesQuery } from '@/application/query/'; import { ConvertCurrency, CreateCurrencyRate, @@ -9,6 +9,7 @@ import { PgPromiseAdapter } from '@/infra/database/'; import { ConvertCurrencyController, CreateCurrencyController, + CurrenciesRoutes, DeleteCurrencyController, ExpressAdapter, GetCurrencyByCodeQueryController, @@ -40,9 +41,12 @@ const getCurrencyByCodeController = new GetCurrencyByCodeQueryController( const deleteCurrencyController = new DeleteCurrencyController( deleteCurrencyRateByCode ); -httpServer.register('get', '/currencies', listCurrenciesController); -httpServer.register('post', '/currencies', createCurrencyController); -httpServer.register('get', '/currencies/convert', convertCurrencyController); -httpServer.register('get', '/currencies/{code}', getCurrencyByCodeController); -httpServer.register('delete', '/currencies/{code}', deleteCurrencyController); +const currencyRoutes = new CurrenciesRoutes({ + listCurrenciesController, + createCurrencyController, + convertCurrencyController, + getCurrencyByCodeController, + deleteCurrencyController, +}); +currencyRoutes.registerRoutes(httpServer); httpServer.listen(3000); diff --git a/src/main/main-express.ts b/src/main/main-express.ts index 63c0b55ba..4d88acde4 100644 --- a/src/main/main-express.ts +++ b/src/main/main-express.ts @@ -7,6 +7,7 @@ import { import { ConvertCurrencyController, CreateCurrencyController, + CurrenciesRoutes, DeleteCurrencyController, ExpressAdapter, GetCurrencyByCodeController, @@ -33,8 +34,11 @@ const getCurrencyByCodeController = new GetCurrencyByCodeController( const deleteCurrencyController = new DeleteCurrencyController( deleteCurrencyRateByCode ); -httpServer.register('post', '/currencies', createCurrencyController); -httpServer.register('get', '/currencies/convert', convertCurrencyController); -httpServer.register('get', '/currencies/{code}', getCurrencyByCodeController); -httpServer.register('delete', '/currencies/{code}', deleteCurrencyController); +const currencyRoutes = new CurrenciesRoutes({ + createCurrencyController, + convertCurrencyController, + getCurrencyByCodeController, + deleteCurrencyController, +}); +currencyRoutes.registerRoutes(httpServer); httpServer.listen(3000); diff --git a/src/main/main-gateway.ts b/src/main/main-gateway.ts index 974f08cb0..e17b06a9e 100644 --- a/src/main/main-gateway.ts +++ b/src/main/main-gateway.ts @@ -9,6 +9,7 @@ import { CurrenciesApiGatewayStatic } from '@/infra/gateway'; import { ConvertCurrencyController, CreateCurrencyController, + CurrenciesRoutes, DeleteCurrencyController, ExpressAdapter, GetCurrencyByCodeController, @@ -37,8 +38,11 @@ const getCurrencyByCodeController = new GetCurrencyByCodeController( const deleteCurrencyController = new DeleteCurrencyController( deleteCurrencyRateByCode ); -httpServer.register('post', '/currencies', createCurrencyController); -httpServer.register('get', '/currencies/convert', convertCurrencyController); -httpServer.register('get', '/currencies/{code}', getCurrencyByCodeController); -httpServer.register('delete', '/currencies/{code}', deleteCurrencyController); +const currencyRoutes = new CurrenciesRoutes({ + createCurrencyController, + convertCurrencyController, + getCurrencyByCodeController, + deleteCurrencyController, +}); +currencyRoutes.registerRoutes(httpServer); httpServer.listen(3000); diff --git a/src/main/main-hapi.ts b/src/main/main-hapi.ts index ae8c244b1..e78d2b53a 100644 --- a/src/main/main-hapi.ts +++ b/src/main/main-hapi.ts @@ -7,6 +7,7 @@ import { import { ConvertCurrencyController, CreateCurrencyController, + CurrenciesRoutes, DeleteCurrencyController, GetCurrencyByCodeController, HapiAdapter, @@ -33,8 +34,11 @@ const getCurrencyByCodeController = new GetCurrencyByCodeController( const deleteCurrencyController = new DeleteCurrencyController( deleteCurrencyRateByCode ); -httpServer.register('post', '/currencies', createCurrencyController); -httpServer.register('get', '/currencies/convert', convertCurrencyController); -httpServer.register('get', '/currencies/{code}', getCurrencyByCodeController); -httpServer.register('delete', '/currencies/{code}', deleteCurrencyController); +const currencyRoutes = new CurrenciesRoutes({ + createCurrencyController, + convertCurrencyController, + getCurrencyByCodeController, + deleteCurrencyController, +}); +currencyRoutes.registerRoutes(httpServer); httpServer.listen(3000); From e209bd60d1e08c7db7c17c11f6568a3417de1d79 Mon Sep 17 00:00:00 2001 From: Leandro Reis Date: Fri, 14 Jun 2024 16:39:00 -0300 Subject: [PATCH 32/43] refactor: update route paths to use '/api/v1' prefix --- src/infra/http-server/currencies.routes.ts | 18 +++++++++++++----- src/infra/http-server/swagger.json | 2 +- test/integration/api.test.ts | 22 ++++++++++++++-------- 3 files changed, 28 insertions(+), 14 deletions(-) diff --git a/src/infra/http-server/currencies.routes.ts b/src/infra/http-server/currencies.routes.ts index 557b455fd..c834ff4ab 100644 --- a/src/infra/http-server/currencies.routes.ts +++ b/src/infra/http-server/currencies.routes.ts @@ -18,22 +18,30 @@ export class CurrenciesRoutes { registerRoutes(httpServer: HttpServer) { if (this.listCurrenciesController) { - httpServer.register('get', '/currencies', this.listCurrenciesController); + httpServer.register( + 'get', + '/api/v1/currencies', + this.listCurrenciesController + ); } - httpServer.register('post', '/currencies', this.createCurrencyController); + httpServer.register( + 'post', + '/api/v1/currencies', + this.createCurrencyController + ); httpServer.register( 'get', - '/currencies/convert', + '/api/v1/currencies/convert', this.convertCurrencyController ); httpServer.register( 'get', - '/currencies/{code}', + '/api/v1/currencies/{code}', this.getCurrencyByCodeController ); httpServer.register( 'delete', - '/currencies/{code}', + '/api/v1/currencies/{code}', this.deleteCurrencyController ); } diff --git a/src/infra/http-server/swagger.json b/src/infra/http-server/swagger.json index 91d35772a..b0bf13471 100644 --- a/src/infra/http-server/swagger.json +++ b/src/infra/http-server/swagger.json @@ -15,7 +15,7 @@ }, "servers": [ { - "url": "http://localhost:3000/", + "url": "http://localhost:3000/api/v1", "description": "Servidor Local" } ], diff --git a/test/integration/api.test.ts b/test/integration/api.test.ts index f2aded80d..b8e29ae6c 100644 --- a/test/integration/api.test.ts +++ b/test/integration/api.test.ts @@ -4,7 +4,7 @@ describe('Main API', () => { it('should convert USD to BRL', async () => { const params = { from: 'USD', to: 'BRL', amount: 10 }; const response = await axios.get( - 'http://localhost:3000/currencies/convert', + 'http://localhost:3000/api/v1/currencies/convert', { params } ); const output = response.data; @@ -18,12 +18,14 @@ describe('Main API', () => { }); it('should convert BRL to USD', async () => { - const brlResponse = await axios.get('http://localhost:3000/currencies/BRL'); + const brlResponse = await axios.get( + 'http://localhost:3000/api/v1/currencies/BRL' + ); const brlOutput = brlResponse.data; const amount = brlOutput.rate * 2; const params = { from: 'brl', to: 'usd', amount: amount }; const response = await axios.get( - 'http://localhost:3000/currencies/convert', + 'http://localhost:3000/api/v1/currencies/convert', { params } ); const output = response.data; @@ -46,9 +48,9 @@ describe('Main API', () => { equivalentCurrencyAmount: 1, equivalentCurrencyCode: 'USD', }; - await axios.post('http://localhost:3000/currencies', payload); + await axios.post('http://localhost:3000/api/v1/currencies', payload); const getResponse = await axios.get( - `http://localhost:3000/currencies/${payload.code}` + `http://localhost:3000/api/v1/currencies/${payload.code}` ); const getOutput = getResponse.data; expect(getOutput.name).toEqual(payload.name); @@ -57,7 +59,7 @@ describe('Main API', () => { expect(getOutput.decimalPoints).toEqual(payload.decimalPoints); expect(getOutput.rate).toEqual(2); const deleteResponse = await axios.delete( - `http://localhost:3000/currencies/${getOutput.code}` + `http://localhost:3000/api/v1/currencies/${getOutput.code}` ); const deleteOutput = deleteResponse.data; expect(deleteOutput).toEqual({ message: 'Currency deleted' }); @@ -66,7 +68,9 @@ describe('Main API', () => { it('should throw an error when trying to convert with invalid currency', async () => { const params = { from: 'INVALID', to: 'BRL', amount: 10 }; try { - await axios.get('http://localhost:3000/currencies/convert', { params }); + await axios.get('http://localhost:3000/api/v1/currencies/convert', { + params, + }); } catch (error: any) { expect(error.response.data).toEqual({ message: 'Currency rate not found: INVALID', @@ -77,7 +81,9 @@ describe('Main API', () => { it('should throw an error when trying to convert with invalid amount', async () => { const params = { from: 'USD', to: 'BRL', amount: -10 }; try { - await axios.get('http://localhost:3000/currencies/convert', { params }); + await axios.get('http://localhost:3000/api/v1/currencies/convert', { + params, + }); } catch (error: any) { expect(error.response.data).toEqual({ message: 'Invalid amount' }); } From 1a24156ff11123973a0e92d2c99c27a63f94fd00 Mon Sep 17 00:00:00 2001 From: Leandro Reis Date: Fri, 14 Jun 2024 16:49:59 -0300 Subject: [PATCH 33/43] refactor: replace "decimalPoints" to "decimalDigits" --- .../query/get-currency-by-code-query.ts | 4 ++-- src/application/query/list-currencies-query.ts | 4 ++-- src/application/use-case/convert-currency.ts | 4 ++-- src/application/use-case/create-currency-rate.ts | 4 ++-- .../use-case/get-currency-rate-by-code.ts | 4 ++-- src/domain/entity/currency-rate.ts | 16 ++++++++-------- .../{decimal-points.ts => decimal-digits.ts} | 4 ++-- src/domain/value-object/index.ts | 2 +- .../http-server/convert-currency.controller.ts | 4 ++-- .../http-server/create-currency.controller.ts | 2 +- src/infra/http-server/swagger.json | 4 ++-- .../currency-rate-repository-database.ts | 2 +- test/integration/api.test.ts | 8 ++++---- test/unity/create-currency-rate.test.ts | 8 ++++---- test/unity/delete-currency-rate.test.ts | 2 +- 15 files changed, 36 insertions(+), 36 deletions(-) rename src/domain/value-object/{decimal-points.ts => decimal-digits.ts} (79%) diff --git a/src/application/query/get-currency-by-code-query.ts b/src/application/query/get-currency-by-code-query.ts index c34c0f2cd..d6dc3ef4e 100644 --- a/src/application/query/get-currency-by-code-query.ts +++ b/src/application/query/get-currency-by-code-query.ts @@ -24,7 +24,7 @@ export class GetCurrencyByCodeQuery { name: row.name, code: row.code, symbol: row.symbol, - decimalPoints: row.decimal_digits, + decimalDigits: row.decimal_digits, rate: Number(row.rate), }; } @@ -39,6 +39,6 @@ type Output = { name: string; code: string; symbol: string; - decimalPoints: number; + decimalDigits: number; rate: number; }; diff --git a/src/application/query/list-currencies-query.ts b/src/application/query/list-currencies-query.ts index c0ece2a07..7681ea940 100644 --- a/src/application/query/list-currencies-query.ts +++ b/src/application/query/list-currencies-query.ts @@ -66,7 +66,7 @@ export class ListCurrenciesQuery { name: row.name, code: row.code, symbol: row.symbol, - decimalPoints: row.decimal_digits, + decimalDigits: row.decimal_digits, rate: Number(row.rate), })), }; @@ -93,7 +93,7 @@ type Output = { name: string; code: string; symbol: string; - decimalPoints: number; + decimalDigits: number; rate: number; }>; }; diff --git a/src/application/use-case/convert-currency.ts b/src/application/use-case/convert-currency.ts index 036bf4f6b..e2455303d 100644 --- a/src/application/use-case/convert-currency.ts +++ b/src/application/use-case/convert-currency.ts @@ -12,7 +12,7 @@ export class ConvertCurrency { return { value: convertedAmount, symbol: exchangeTo.symbol, - decimalPoints: exchangeTo.decimalPoints, + decimalDigits: exchangeTo.decimalDigits, }; } } @@ -26,7 +26,7 @@ export type ConvertCurrencyDto = { export type ConvertCurrencyResponse = { value: number; symbol: string; - decimalPoints: number; + decimalDigits: number; }; export default ConvertCurrency; diff --git a/src/application/use-case/create-currency-rate.ts b/src/application/use-case/create-currency-rate.ts index cfa1b5e5c..fef74f4ea 100644 --- a/src/application/use-case/create-currency-rate.ts +++ b/src/application/use-case/create-currency-rate.ts @@ -22,7 +22,7 @@ export class CreateCurrencyRate { input.name, input.code, input.symbol, - input.decimalPoints, + input.decimalDigits, rate ); await this.currencyRateRepository.add(currencyRate); @@ -34,7 +34,7 @@ export type CreateCurrencyRateDto = { name: string; symbol: string; code: string; - decimalPoints: number; + decimalDigits: number; baseAmount: number; equivalentCurrencyCode: string; equivalentCurrencyAmount: number; diff --git a/src/application/use-case/get-currency-rate-by-code.ts b/src/application/use-case/get-currency-rate-by-code.ts index 23ab56256..bb98e8ab1 100644 --- a/src/application/use-case/get-currency-rate-by-code.ts +++ b/src/application/use-case/get-currency-rate-by-code.ts @@ -12,7 +12,7 @@ export class GetCurrencyRateByCode { name: currencyRate.name, code: currencyRate.code, symbol: currencyRate.symbol, - decimalPoints: currencyRate.decimalPoints, + decimalDigits: currencyRate.decimalDigits, rate: currencyRate.rate, }; } @@ -23,7 +23,7 @@ export type GetCurrencyRateByCodeOutput = { name: string; code: string; symbol: string; - decimalPoints: number; + decimalDigits: number; rate: number; }; diff --git a/src/domain/entity/currency-rate.ts b/src/domain/entity/currency-rate.ts index 4e8117ddb..0bbdb5a02 100644 --- a/src/domain/entity/currency-rate.ts +++ b/src/domain/entity/currency-rate.ts @@ -2,7 +2,7 @@ import currency from 'currency.js'; import { Code, - DecimalPoints, + DecimalDigits, Symbol as MySymbol, Name, Rate, @@ -12,7 +12,7 @@ export class CurrencyRate { private _name: Name; private _code: Code; private _symbol: MySymbol; - private _decimalPoints: DecimalPoints; + private _decimalPoints: DecimalDigits; private _rate: Rate; constructor( @@ -20,13 +20,13 @@ export class CurrencyRate { name: string, code: string, symbol: string, - decimalPoints: number, + decimalDigits: number, rate: number ) { this._name = new Name(name); this._code = new Code(code); this._symbol = new MySymbol(symbol); - this._decimalPoints = new DecimalPoints(decimalPoints); + this._decimalPoints = new DecimalDigits(decimalDigits); this._rate = new Rate(rate); } @@ -34,11 +34,11 @@ export class CurrencyRate { name: string, code: string, symbol: string, - decimalPoints: number, + decimalDigits: number, rate: number ): CurrencyRate { const id = crypto.randomUUID(); - return new CurrencyRate(id, name, code, symbol, decimalPoints, rate); + return new CurrencyRate(id, name, code, symbol, decimalDigits, rate); } get name(): string { @@ -53,7 +53,7 @@ export class CurrencyRate { return this._symbol.value; } - get decimalPoints(): number { + get decimalDigits(): number { return this._decimalPoints.value; } @@ -67,7 +67,7 @@ export class CurrencyRate { } const convertedAmount = (amount * currencyRate.rate) / this.rate; const amountCurrency = currency(convertedAmount, { - precision: currencyRate.decimalPoints, + precision: currencyRate.decimalDigits, }); return amountCurrency.value; } diff --git a/src/domain/value-object/decimal-points.ts b/src/domain/value-object/decimal-digits.ts similarity index 79% rename from src/domain/value-object/decimal-points.ts rename to src/domain/value-object/decimal-digits.ts index e7ae95231..24caa984f 100644 --- a/src/domain/value-object/decimal-points.ts +++ b/src/domain/value-object/decimal-digits.ts @@ -1,4 +1,4 @@ -export class DecimalPoints { +export class DecimalDigits { private _value: number; constructor(value: number) { @@ -13,4 +13,4 @@ export class DecimalPoints { } } -export default DecimalPoints; +export default DecimalDigits; diff --git a/src/domain/value-object/index.ts b/src/domain/value-object/index.ts index 6d8f1ea91..f24e02caf 100644 --- a/src/domain/value-object/index.ts +++ b/src/domain/value-object/index.ts @@ -1,5 +1,5 @@ export * from './code'; -export * from './decimal-points'; +export * from './decimal-digits'; export * from './name'; export * from './rate'; export * from './symbol'; diff --git a/src/infra/http-server/convert-currency.controller.ts b/src/infra/http-server/convert-currency.controller.ts index eb1dfd1da..db37475e6 100644 --- a/src/infra/http-server/convert-currency.controller.ts +++ b/src/infra/http-server/convert-currency.controller.ts @@ -18,9 +18,9 @@ export class ConvertCurrencyController implements Controller { to: input.to, givenAmount: input.amount, convertedAmount: output.value, - decimalPoints: output.decimalPoints, + decimalDigits: output.decimalDigits, symbol: output.symbol, - convertedAmountFormatted: `${output.symbol} ${output.value.toFixed(output.decimalPoints)}`, + convertedAmountFormatted: `${output.symbol} ${output.value.toFixed(output.decimalDigits)}`, }, }; } diff --git a/src/infra/http-server/create-currency.controller.ts b/src/infra/http-server/create-currency.controller.ts index 065741a8f..bc4ed9274 100644 --- a/src/infra/http-server/create-currency.controller.ts +++ b/src/infra/http-server/create-currency.controller.ts @@ -10,7 +10,7 @@ export class CreateCurrencyController implements Controller { name: body.name, code: body.code, symbol: body.symbol, - decimalPoints: body.decimalPoints, + decimalDigits: body.decimalDigits, baseAmount: body.baseAmount, equivalentCurrencyAmount: body.equivalentCurrencyAmount, equivalentCurrencyCode: body.equivalentCurrencyCode, diff --git a/src/infra/http-server/swagger.json b/src/infra/http-server/swagger.json index b0bf13471..931c882ea 100644 --- a/src/infra/http-server/swagger.json +++ b/src/infra/http-server/swagger.json @@ -205,7 +205,7 @@ "givenAmount": { "type": "number" }, "convertedAmount": { "type": "number" }, "convertedAmountFormatted": { "type": "string" }, - "decimalPoints": { "type": "number" }, + "decimalDigits": { "type": "number" }, "symbol": { "type": "string" } } } @@ -321,7 +321,7 @@ "name": { "type": "string" }, "code": { "type": "string" }, "symbol": { "type": "string" }, - "decimalPoints": { "type": "number", "format": "integer" }, + "decimalDigits": { "type": "number", "format": "integer" }, "rate": { "type": "number", "format": "double" } } } diff --git a/src/infra/repository/currency-rate-repository-database.ts b/src/infra/repository/currency-rate-repository-database.ts index 79db794be..1e5ca3500 100644 --- a/src/infra/repository/currency-rate-repository-database.ts +++ b/src/infra/repository/currency-rate-repository-database.ts @@ -42,7 +42,7 @@ export class CurrencyRateRepositoryDatabase implements CurrencyRateRepository { currencyRate.name, currencyRate.code, currencyRate.symbol, - currencyRate.decimalPoints, + currencyRate.decimalDigits, ] ); diff --git a/test/integration/api.test.ts b/test/integration/api.test.ts index b8e29ae6c..95365775b 100644 --- a/test/integration/api.test.ts +++ b/test/integration/api.test.ts @@ -13,7 +13,7 @@ describe('Main API', () => { expect(output.givenAmount).toBe(10); expect(output.convertedAmount).toBeGreaterThan(0); expect(output.convertedAmountFormatted).toContain('R$'); - expect(output.decimalPoints).toBe(2); + expect(output.decimalDigits).toBe(2); expect(output.symbol).toBe('R$'); }); @@ -34,7 +34,7 @@ describe('Main API', () => { expect(output.givenAmount).toBe(amount); expect(output.convertedAmount).toBe(2.0); expect(output.convertedAmountFormatted).toContain('$'); - expect(output.decimalPoints).toBe(2); + expect(output.decimalDigits).toBe(2); expect(output.symbol).toBe('$'); }); @@ -43,7 +43,7 @@ describe('Main API', () => { name: 'Any Currency', code: 'ANY', symbol: '$', - decimalPoints: 2, + decimalDigits: 2, baseAmount: 2, equivalentCurrencyAmount: 1, equivalentCurrencyCode: 'USD', @@ -56,7 +56,7 @@ describe('Main API', () => { expect(getOutput.name).toEqual(payload.name); expect(getOutput.code).toEqual(payload.code); expect(getOutput.symbol).toEqual(payload.symbol); - expect(getOutput.decimalPoints).toEqual(payload.decimalPoints); + expect(getOutput.decimalDigits).toEqual(payload.decimalDigits); expect(getOutput.rate).toEqual(2); const deleteResponse = await axios.delete( `http://localhost:3000/api/v1/currencies/${getOutput.code}` diff --git a/test/unity/create-currency-rate.test.ts b/test/unity/create-currency-rate.test.ts index d06cf5647..66faebe0a 100644 --- a/test/unity/create-currency-rate.test.ts +++ b/test/unity/create-currency-rate.test.ts @@ -12,7 +12,7 @@ describe('create-currency-rate', () => { name: 'Grand Theft Auto Dollar', symbol: 'GTA$', code: 'GTA', - decimalPoints: 2, + decimalDigits: 2, baseAmount: 1250000.0, equivalentCurrencyCode: 'BRL', equivalentCurrencyAmount: 83.5, @@ -27,8 +27,8 @@ describe('create-currency-rate', () => { expect(getCurrencyOutput.name).toBe(createCurrencyInput.name); expect(getCurrencyOutput.symbol).toBe(createCurrencyInput.symbol); expect(getCurrencyOutput.code).toBe(createCurrencyInput.code); - expect(getCurrencyOutput.decimalPoints).toBe( - createCurrencyInput.decimalPoints + expect(getCurrencyOutput.decimalDigits).toBe( + createCurrencyInput.decimalDigits ); expect(getCurrencyOutput.rate).toBe(77844.31137724551); }); @@ -40,7 +40,7 @@ describe('create-currency-rate', () => { name: 'Any Currency', code: 'ANY', symbol: 'A$', - decimalPoints: 2, + decimalDigits: 2, baseAmount: 1.0, equivalentCurrencyCode: 'USD', equivalentCurrencyAmount: 1.0, diff --git a/test/unity/delete-currency-rate.test.ts b/test/unity/delete-currency-rate.test.ts index fd6218deb..cb7ae39fe 100644 --- a/test/unity/delete-currency-rate.test.ts +++ b/test/unity/delete-currency-rate.test.ts @@ -13,7 +13,7 @@ describe('delete-currency-rate', () => { name: 'Any Currency', symbol: 'AC$', code: 'AYC', - decimalPoints: 2, + decimalDigits: 2, baseAmount: 1.0, equivalentCurrencyCode: 'USD', equivalentCurrencyAmount: 1.0, From 38a371a9b9aa34d2b2bd1dba6229763b11093199 Mon Sep 17 00:00:00 2001 From: Leandro Reis Date: Fri, 14 Jun 2024 16:58:53 -0300 Subject: [PATCH 34/43] refactor: handle currency conversion errors in ConvertCurrencyController - Added error handling in ConvertCurrencyController to catch NotFoundError and return a 404 response with the appropriate message. --- .../convert-currency.controller.ts | 47 ++++++++++++------- test/integration/api.test.ts | 7 +-- 2 files changed, 33 insertions(+), 21 deletions(-) diff --git a/src/infra/http-server/convert-currency.controller.ts b/src/infra/http-server/convert-currency.controller.ts index db37475e6..6772e02c9 100644 --- a/src/infra/http-server/convert-currency.controller.ts +++ b/src/infra/http-server/convert-currency.controller.ts @@ -1,28 +1,39 @@ import { ConvertCurrency } from '@/application/use-case'; +import { NotFoundError } from '@/domain/errors'; import { Controller, Request, Response } from '@/infra/http-server/controller'; export class ConvertCurrencyController implements Controller { constructor(private readonly convertCurrency: ConvertCurrency) {} async handle({ query }: Request): Promise { - const input = { - from: query.from.toUpperCase(), - to: query.to.toUpperCase(), - amount: Number(query.amount), - }; - const output = await this.convertCurrency.execute(input); - return { - statusCode: 200, - data: { - from: input.from, - to: input.to, - givenAmount: input.amount, - convertedAmount: output.value, - decimalDigits: output.decimalDigits, - symbol: output.symbol, - convertedAmountFormatted: `${output.symbol} ${output.value.toFixed(output.decimalDigits)}`, - }, - }; + try { + const input = { + from: query.from.toUpperCase(), + to: query.to.toUpperCase(), + amount: Number(query.amount), + }; + const output = await this.convertCurrency.execute(input); + return { + statusCode: 200, + data: { + from: input.from, + to: input.to, + givenAmount: input.amount, + convertedAmount: output.value, + decimalDigits: output.decimalDigits, + symbol: output.symbol, + convertedAmountFormatted: `${output.symbol} ${output.value.toFixed(output.decimalDigits)}`, + }, + }; + } catch (error: any) { + if (error instanceof NotFoundError) { + return { + statusCode: 404, + data: { message: error.message }, + }; + } + throw error; + } } } diff --git a/test/integration/api.test.ts b/test/integration/api.test.ts index 95365775b..720e178f4 100644 --- a/test/integration/api.test.ts +++ b/test/integration/api.test.ts @@ -72,9 +72,10 @@ describe('Main API', () => { params, }); } catch (error: any) { - expect(error.response.data).toEqual({ - message: 'Currency rate not found: INVALID', - }); + expect(error.response.data.message).toBe( + 'Currency rate not found: INVALID' + ); + expect(error.response.status).toBe(404); } }); From d3f90433e7b2f3164695b0d07a22fbb4d2458d09 Mon Sep 17 00:00:00 2001 From: Leandro Reis Date: Fri, 14 Jun 2024 17:09:42 -0300 Subject: [PATCH 35/43] refactor: handle bad request error in ConvertCurrencyController --- src/domain/entity/currency-rate.ts | 3 ++- src/infra/http-server/convert-currency.controller.ts | 8 +++++++- test/integration/api.test.ts | 3 ++- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/src/domain/entity/currency-rate.ts b/src/domain/entity/currency-rate.ts index 0bbdb5a02..4165aaff4 100644 --- a/src/domain/entity/currency-rate.ts +++ b/src/domain/entity/currency-rate.ts @@ -1,5 +1,6 @@ import currency from 'currency.js'; +import { InvalidParamError } from '@/domain/errors'; import { Code, DecimalDigits, @@ -63,7 +64,7 @@ export class CurrencyRate { convertTo(currencyRate: CurrencyRate, amount: number): number { if (amount < 0) { - throw new Error('Invalid amount'); + throw new InvalidParamError('Invalid amount'); } const convertedAmount = (amount * currencyRate.rate) / this.rate; const amountCurrency = currency(convertedAmount, { diff --git a/src/infra/http-server/convert-currency.controller.ts b/src/infra/http-server/convert-currency.controller.ts index 6772e02c9..6cdd06b73 100644 --- a/src/infra/http-server/convert-currency.controller.ts +++ b/src/infra/http-server/convert-currency.controller.ts @@ -1,5 +1,5 @@ import { ConvertCurrency } from '@/application/use-case'; -import { NotFoundError } from '@/domain/errors'; +import { InvalidParamError, NotFoundError } from '@/domain/errors'; import { Controller, Request, Response } from '@/infra/http-server/controller'; export class ConvertCurrencyController implements Controller { @@ -32,6 +32,12 @@ export class ConvertCurrencyController implements Controller { data: { message: error.message }, }; } + if (error instanceof InvalidParamError) { + return { + statusCode: 400, + data: { message: error.message }, + }; + } throw error; } } diff --git a/test/integration/api.test.ts b/test/integration/api.test.ts index 720e178f4..8b8255910 100644 --- a/test/integration/api.test.ts +++ b/test/integration/api.test.ts @@ -86,7 +86,8 @@ describe('Main API', () => { params, }); } catch (error: any) { - expect(error.response.data).toEqual({ message: 'Invalid amount' }); + expect(error.response.data.message).toEqual('Invalid amount'); + expect(error.response.status).toBe(400); } }); }); From 1838a78d7a79d3e5cb61fff4028868c6c95e7487 Mon Sep 17 00:00:00 2001 From: Leandro Reis Date: Fri, 14 Jun 2024 17:22:13 -0300 Subject: [PATCH 36/43] refactor: update currency error handling and messages - Renamed 'currency-rate-not-found.error.ts' to 'currency-not-found.error.ts'; - Updated 'delete-currency.controller.ts' to handle 'CurrencyNotFoundError' and return a 404 response. --- src/application/query/get-currency-by-code-query.ts | 4 ++-- .../use-case/delete-currency-rate-by-code.ts | 3 ++- src/domain/errors/currency-not-found.error.ts | 10 ++++++++++ src/domain/errors/currency-rate-not-found.error.ts | 10 ---------- src/domain/errors/index.ts | 2 +- src/infra/http-server/delete-currency.controller.ts | 8 +++++++- .../repository/currency-rate-repository-api.ts | 4 ++-- .../repository/currency-rate-repository-database.ts | 4 ++-- .../repository/currency-rate-repository-fake.ts | 4 ++-- test/integration/api.test.ts | 13 ++++++++++--- test/unity/convert-currency.test.ts | 2 +- test/unity/delete-currency-rate.test.ts | 4 ++-- 12 files changed, 41 insertions(+), 27 deletions(-) create mode 100644 src/domain/errors/currency-not-found.error.ts delete mode 100644 src/domain/errors/currency-rate-not-found.error.ts diff --git a/src/application/query/get-currency-by-code-query.ts b/src/application/query/get-currency-by-code-query.ts index d6dc3ef4e..375217a59 100644 --- a/src/application/query/get-currency-by-code-query.ts +++ b/src/application/query/get-currency-by-code-query.ts @@ -1,4 +1,4 @@ -import { CurrencyRateNotFoundError } from '@/domain/errors'; +import { CurrencyNotFoundError } from '@/domain/errors'; import { DatabaseConnection } from '@/infra/database'; export class GetCurrencyByCodeQuery { @@ -17,7 +17,7 @@ export class GetCurrencyByCodeQuery { [code] ); if (!row) { - throw new CurrencyRateNotFoundError(code); + throw new CurrencyNotFoundError(code); } return { id: row.currency_id, diff --git a/src/application/use-case/delete-currency-rate-by-code.ts b/src/application/use-case/delete-currency-rate-by-code.ts index 4ba511f70..d3bbf6fc1 100644 --- a/src/application/use-case/delete-currency-rate-by-code.ts +++ b/src/application/use-case/delete-currency-rate-by-code.ts @@ -1,4 +1,5 @@ import { CurrencyRateRepository } from '@/application/repository'; +import { CurrencyNotFoundError } from '@/domain/errors'; export class DeleteCurrencyRateByCode { constructor( @@ -11,7 +12,7 @@ export class DeleteCurrencyRateByCode { } const currencyRate = await this.currencyRateRepository.findByCode(code); if (!currencyRate) { - throw new Error('Currency rate not found'); + throw new CurrencyNotFoundError(code); } await this.currencyRateRepository.delete(currencyRate.code); } diff --git a/src/domain/errors/currency-not-found.error.ts b/src/domain/errors/currency-not-found.error.ts new file mode 100644 index 000000000..d6d084ad2 --- /dev/null +++ b/src/domain/errors/currency-not-found.error.ts @@ -0,0 +1,10 @@ +import NotFoundError from './not-found.error'; + +export class CurrencyNotFoundError extends NotFoundError { + constructor(currencyRateIdOrCode: string) { + super(`Currency not found: ${currencyRateIdOrCode}`); + this.name = 'CurrencyNotFoundError'; + } +} + +export default CurrencyNotFoundError; diff --git a/src/domain/errors/currency-rate-not-found.error.ts b/src/domain/errors/currency-rate-not-found.error.ts deleted file mode 100644 index 89631d06a..000000000 --- a/src/domain/errors/currency-rate-not-found.error.ts +++ /dev/null @@ -1,10 +0,0 @@ -import NotFoundError from './not-found.error'; - -export class CurrencyRateNotFoundError extends NotFoundError { - constructor(currencyRateIdOrCode: string) { - super(`Currency rate not found: ${currencyRateIdOrCode}`); - this.name = 'CurrencyRateNotFoundError'; - } -} - -export default CurrencyRateNotFoundError; diff --git a/src/domain/errors/index.ts b/src/domain/errors/index.ts index d65020034..8bdc60ab1 100644 --- a/src/domain/errors/index.ts +++ b/src/domain/errors/index.ts @@ -1,3 +1,3 @@ -export * from './currency-rate-not-found.error'; +export * from './currency-not-found.error'; export * from './invalid-param.error'; export * from './not-found.error'; diff --git a/src/infra/http-server/delete-currency.controller.ts b/src/infra/http-server/delete-currency.controller.ts index 3e7a89183..5bbbd38bf 100644 --- a/src/infra/http-server/delete-currency.controller.ts +++ b/src/infra/http-server/delete-currency.controller.ts @@ -1,4 +1,5 @@ import { DeleteCurrencyRateByCode } from '@/application/use-case'; +import { CurrencyNotFoundError } from '@/domain/errors'; import { Controller, Request, Response } from '@/infra/http-server/controller'; export class DeleteCurrencyController implements Controller { @@ -17,7 +18,12 @@ export class DeleteCurrencyController implements Controller { }, }; } catch (error: any) { - console.error(error.message); + if (error instanceof CurrencyNotFoundError) { + return { + statusCode: 404, + data: { message: error.message }, + }; + } throw error; } } diff --git a/src/infra/repository/currency-rate-repository-api.ts b/src/infra/repository/currency-rate-repository-api.ts index c34012d94..2212dc885 100644 --- a/src/infra/repository/currency-rate-repository-api.ts +++ b/src/infra/repository/currency-rate-repository-api.ts @@ -1,6 +1,6 @@ import { CurrencyRateRepository } from '@/application/repository/'; import { CurrencyRate } from '@/domain/entity/'; -import { CurrencyRateNotFoundError } from '@/domain/errors/'; +import { CurrencyNotFoundError } from '@/domain/errors/'; import { CurrencyApiGateway } from '@/infra/gateway'; export class CurrencyRateRepositoryApi implements CurrencyRateRepository { @@ -19,7 +19,7 @@ export class CurrencyRateRepositoryApi implements CurrencyRateRepository { }); const currencyData = currencyResponse.data[code]; if (!currencyResponse.data[code] || !currencyResponse.data[code]) { - throw new CurrencyRateNotFoundError(code); + throw new CurrencyNotFoundError(code); } const rateResponse = await this.currencyGateway.getRates({ currencies: [code], diff --git a/src/infra/repository/currency-rate-repository-database.ts b/src/infra/repository/currency-rate-repository-database.ts index 1e5ca3500..0630edc5c 100644 --- a/src/infra/repository/currency-rate-repository-database.ts +++ b/src/infra/repository/currency-rate-repository-database.ts @@ -1,6 +1,6 @@ import { CurrencyRateRepository } from '@/application/repository'; import { CurrencyRate } from '@/domain/entity/'; -import { CurrencyRateNotFoundError } from '@/domain/errors'; +import { CurrencyNotFoundError } from '@/domain/errors'; import { DatabaseConnection } from '@/infra/database'; export class CurrencyRateRepositoryDatabase implements CurrencyRateRepository { @@ -19,7 +19,7 @@ export class CurrencyRateRepositoryDatabase implements CurrencyRateRepository { [code] ); if (!row) { - throw new CurrencyRateNotFoundError(code); + throw new CurrencyNotFoundError(code); } const currencyRate = new CurrencyRate( row.id, diff --git a/src/infra/repository/currency-rate-repository-fake.ts b/src/infra/repository/currency-rate-repository-fake.ts index 0aec90cf7..7889ff99e 100644 --- a/src/infra/repository/currency-rate-repository-fake.ts +++ b/src/infra/repository/currency-rate-repository-fake.ts @@ -1,6 +1,6 @@ import { CurrencyRateRepository } from '@/application/repository/'; import { CurrencyRate } from '@/domain/entity/'; -import { CurrencyRateNotFoundError } from '@/domain/errors/'; +import { CurrencyNotFoundError } from '@/domain/errors/'; export class CurrencyRateRepositoryFake implements CurrencyRateRepository { currencyRates: CurrencyRate[]; @@ -18,7 +18,7 @@ export class CurrencyRateRepositoryFake implements CurrencyRateRepository { (currencyRate) => currencyRate.code === code ); if (!currencyRate) { - throw new CurrencyRateNotFoundError(code); + throw new CurrencyNotFoundError(code); } return currencyRate; } diff --git a/test/integration/api.test.ts b/test/integration/api.test.ts index 8b8255910..6491c2ef0 100644 --- a/test/integration/api.test.ts +++ b/test/integration/api.test.ts @@ -72,9 +72,7 @@ describe('Main API', () => { params, }); } catch (error: any) { - expect(error.response.data.message).toBe( - 'Currency rate not found: INVALID' - ); + expect(error.response.data.message).toBe('Currency not found: INVALID'); expect(error.response.status).toBe(404); } }); @@ -90,4 +88,13 @@ describe('Main API', () => { expect(error.response.status).toBe(400); } }); + + it('should throw an error when trying to delete an invalid currency', async () => { + try { + await axios.delete('http://localhost:3000/api/v1/currencies/INVALID'); + } catch (error: any) { + expect(error.response.data.message).toBe('Currency not found: INVALID'); + expect(error.response.status).toBe(404); + } + }); }); diff --git a/test/unity/convert-currency.test.ts b/test/unity/convert-currency.test.ts index 6786882e5..e2af27c97 100644 --- a/test/unity/convert-currency.test.ts +++ b/test/unity/convert-currency.test.ts @@ -57,7 +57,7 @@ describe('Currency Converter', () => { amount: 1, }; await expect(convertCurrency.execute(input)).rejects.toThrow( - 'Currency rate not found' + 'Currency not found' ); }); diff --git a/test/unity/delete-currency-rate.test.ts b/test/unity/delete-currency-rate.test.ts index cb7ae39fe..3199b1e19 100644 --- a/test/unity/delete-currency-rate.test.ts +++ b/test/unity/delete-currency-rate.test.ts @@ -28,7 +28,7 @@ describe('delete-currency-rate', () => { ); await expect( getCurrencyRateByCode.execute(createCurrencyInput.code) - ).rejects.toThrow('Currency rate not found'); + ).rejects.toThrow('Currency not found'); }); it('should throw an error when trying to delete a currency rate that does not exist', async () => { @@ -37,7 +37,7 @@ describe('delete-currency-rate', () => { currencyRateRepository ); await expect(deleteCurrencyRate.execute('ABC')).rejects.toThrow( - 'Currency rate not found' + 'Currency not found' ); }); From 68a216ff91d77564cb900097d394cc641f95dcb4 Mon Sep 17 00:00:00 2001 From: Leandro Reis Date: Fri, 14 Jun 2024 17:51:05 -0300 Subject: [PATCH 37/43] refactor: handle InvalidParamError on create currency --- src/domain/value-object/code.ts | 4 +- src/domain/value-object/decimal-digits.ts | 4 +- src/domain/value-object/name.ts | 4 +- src/domain/value-object/rate.ts | 4 +- src/domain/value-object/symbol.ts | 4 +- .../http-server/create-currency.controller.ts | 8 +- test/integration/api.test.ts | 18 ++++ test/unity/currenty-rate.test.ts | 86 +++++++++++++------ 8 files changed, 98 insertions(+), 34 deletions(-) diff --git a/src/domain/value-object/code.ts b/src/domain/value-object/code.ts index 425de9364..4c4d75f5d 100644 --- a/src/domain/value-object/code.ts +++ b/src/domain/value-object/code.ts @@ -1,9 +1,11 @@ +import { InvalidParamError } from '../errors'; + export class Code { private _value: string; constructor(value: string) { if (value.length < 3 || value.length > 4) { - throw new Error('Invalid code'); + throw new InvalidParamError('Invalid code'); } this._value = value.toUpperCase(); } diff --git a/src/domain/value-object/decimal-digits.ts b/src/domain/value-object/decimal-digits.ts index 24caa984f..571dd8304 100644 --- a/src/domain/value-object/decimal-digits.ts +++ b/src/domain/value-object/decimal-digits.ts @@ -1,9 +1,11 @@ +import { InvalidParamError } from '../errors'; + export class DecimalDigits { private _value: number; constructor(value: number) { if (value < 0 || value > 10) { - throw new Error('Invalid decimal point'); + throw new InvalidParamError('Invalid decimal digits'); } this._value = value; } diff --git a/src/domain/value-object/name.ts b/src/domain/value-object/name.ts index 5f8222128..7845f4514 100644 --- a/src/domain/value-object/name.ts +++ b/src/domain/value-object/name.ts @@ -1,9 +1,11 @@ +import { InvalidParamError } from '../errors'; + export class Name { private _value: string; constructor(value: string) { if (value.length < 3 || value.length > 100) { - throw new Error('Invalid name'); + throw new InvalidParamError('Invalid name'); } this._value = value; } diff --git a/src/domain/value-object/rate.ts b/src/domain/value-object/rate.ts index 080a28d47..b3a756cf3 100644 --- a/src/domain/value-object/rate.ts +++ b/src/domain/value-object/rate.ts @@ -1,9 +1,11 @@ +import { InvalidParamError } from '../errors'; + export class Rate { private _value: number; constructor(value: number) { if (value <= 0) { - throw new Error('Invalid rate'); + throw new InvalidParamError('Invalid rate'); } this._value = value; } diff --git a/src/domain/value-object/symbol.ts b/src/domain/value-object/symbol.ts index a2c8e53cb..5a61e5a22 100644 --- a/src/domain/value-object/symbol.ts +++ b/src/domain/value-object/symbol.ts @@ -1,9 +1,11 @@ +import { InvalidParamError } from '../errors'; + export class Symbol { private _value: string; constructor(value: string) { if (value.length < 1 || value.length > 5) { - throw new Error('Invalid symbol'); + throw new InvalidParamError('Invalid symbol'); } this._value = value; } diff --git a/src/infra/http-server/create-currency.controller.ts b/src/infra/http-server/create-currency.controller.ts index bc4ed9274..a47ad0193 100644 --- a/src/infra/http-server/create-currency.controller.ts +++ b/src/infra/http-server/create-currency.controller.ts @@ -1,4 +1,5 @@ import { CreateCurrencyRate } from '@/application/use-case'; +import { InvalidParamError } from '@/domain/errors'; import { Controller, Request, Response } from '@/infra/http-server/controller'; export class CreateCurrencyController implements Controller { @@ -21,7 +22,12 @@ export class CreateCurrencyController implements Controller { data: output, }; } catch (error: any) { - console.error(error.message); + if (error instanceof InvalidParamError) { + return { + statusCode: 400, + data: { message: error.message }, + }; + } throw error; } } diff --git a/test/integration/api.test.ts b/test/integration/api.test.ts index 6491c2ef0..70c3ffb29 100644 --- a/test/integration/api.test.ts +++ b/test/integration/api.test.ts @@ -65,6 +65,24 @@ describe('Main API', () => { expect(deleteOutput).toEqual({ message: 'Currency deleted' }); }); + it('should throw an error when trying to create a currency with invalid name', async () => { + const payload = { + name: '', + code: 'ANY', + symbol: '$', + decimalDigits: 2, + baseAmount: 2, + equivalentCurrencyAmount: 1, + equivalentCurrencyCode: 'USD', + }; + try { + await axios.post('http://localhost:3000/api/v1/currencies', payload); + } catch (error: any) { + expect(error.response.data.message).toBe('Invalid name'); + expect(error.response.status).toBe(400); + } + }); + it('should throw an error when trying to convert with invalid currency', async () => { const params = { from: 'INVALID', to: 'BRL', amount: 10 }; try { diff --git a/test/unity/currenty-rate.test.ts b/test/unity/currenty-rate.test.ts index ea1e9368c..adc8e07d4 100644 --- a/test/unity/currenty-rate.test.ts +++ b/test/unity/currenty-rate.test.ts @@ -1,4 +1,5 @@ import { CurrencyRate } from '@/domain/entity/'; +import { InvalidParamError } from '@/domain/errors'; describe('Currency Rate', () => { it('should create a currency rate', () => { @@ -22,47 +23,76 @@ describe('Currency Rate', () => { }); it('should throw an error when the rate is invalid', () => { - expect(() => CurrencyRate.create('US Dollar', 'USD', '$', 2, 0)).toThrow( - 'Invalid rate' - ); - expect(() => CurrencyRate.create('US Dollar', 'USD', '$', 2, -1)).toThrow( - 'Invalid rate' - ); + try { + CurrencyRate.create('US Dollar', 'USD', '$', 2, 0); + fail('Expected to throw an error'); + } catch (error: any) { + expect(error).toBeInstanceOf(InvalidParamError); + expect(error.message).toBe('Invalid rate'); + } + try { + CurrencyRate.create('US Dollar', 'USD', '$', 2, -1); + fail('Expected to throw an error'); + } catch (error: any) { + expect(error).toBeInstanceOf(InvalidParamError); + expect(error.message).toBe('Invalid rate'); + } }); it('should throw an error when the name is invalid', () => { - expect(() => CurrencyRate.create('', 'USD', '$', 2, 1)).toThrow( - 'Invalid name' - ); + try { + CurrencyRate.create('', 'USD', '$', 2, 1); + fail('Expected to throw an error'); + } catch (error: any) { + expect(error).toBeInstanceOf(InvalidParamError); + expect(error.message).toBe('Invalid name'); + } }); it('should throw an error when the code is invalid', () => { - expect(() => CurrencyRate.create('US Dollar', '', '$', 2, 1)).toThrow( - 'Invalid code' - ); + try { + CurrencyRate.create('US Dollar', '', '$', 2, 1); + fail('Expected to throw an error'); + } catch (error: any) { + expect(error).toBeInstanceOf(InvalidParamError); + expect(error.message).toBe('Invalid code'); + } }); it('should throw an error when the symbol is invalid', () => { - expect(() => CurrencyRate.create('US Dollar', 'USD', '', 2, 1)).toThrow( - 'Invalid symbol' - ); + try { + CurrencyRate.create('US Dollar', 'USD', '', 2, 1); + fail('Expected to throw an error'); + } catch (error: any) { + expect(error).toBeInstanceOf(InvalidParamError); + expect(error.message).toBe('Invalid symbol'); + } }); - it('should throw an error when the decimal points is invalid', () => { - expect(() => CurrencyRate.create('US Dollar', 'USD', '$', -1, 1)).toThrow( - 'Invalid decimal point' - ); - expect(() => CurrencyRate.create('US Dollar', 'USD', '$', 11, 1)).toThrow( - 'Invalid decimal point' - ); + it('should throw an error when the decimal digits is invalid', () => { + try { + CurrencyRate.create('US Dollar', 'USD', '$', -1, 1); + fail('Expected to throw an error'); + } catch (error: any) { + expect(error).toBeInstanceOf(InvalidParamError); + expect(error.message).toBe('Invalid decimal digits'); + } }); it('should throw an error when the rate is invalid', () => { - expect(() => CurrencyRate.create('US Dollar', 'USD', '$', 2, 0)).toThrow( - 'Invalid rate' - ); - expect(() => CurrencyRate.create('US Dollar', 'USD', '$', 2, -1)).toThrow( - 'Invalid rate' - ); + try { + CurrencyRate.create('US Dollar', 'USD', '$', 2, 0); + fail('Expected to throw an error'); + } catch (error: any) { + expect(error).toBeInstanceOf(InvalidParamError); + expect(error.message).toBe('Invalid rate'); + } + try { + CurrencyRate.create('US Dollar', 'USD', '$', 2, -1); + fail('Expected to throw an error'); + } catch (error: any) { + expect(error).toBeInstanceOf(InvalidParamError); + expect(error.message).toBe('Invalid rate'); + } }); }); From 36ea902bc922fa99ac3393176e5e4137da5055ef Mon Sep 17 00:00:00 2001 From: Leandro Reis Date: Fri, 14 Jun 2024 18:03:19 -0300 Subject: [PATCH 38/43] refactor: update currency error handling and messages --- src/infra/http-server/swagger.json | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/src/infra/http-server/swagger.json b/src/infra/http-server/swagger.json index 931c882ea..f84d839fb 100644 --- a/src/infra/http-server/swagger.json +++ b/src/infra/http-server/swagger.json @@ -155,7 +155,20 @@ "schema": { "type": "object", "properties": { - "error": { "type": "string" } + "message": { "type": "string" } + } + } + } + } + }, + "404": { + "description": "Moeda equivalente não encontrada", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { "type": "string" } } } } @@ -219,7 +232,7 @@ "schema": { "type": "object", "properties": { - "error": { "type": "string" } + "message": { "type": "string" } } } } @@ -260,7 +273,7 @@ "schema": { "type": "object", "properties": { - "error": { "type": "string" } + "message": { "type": "string" } } } } @@ -302,7 +315,7 @@ "schema": { "type": "object", "properties": { - "error": { "type": "string" } + "message": { "type": "string" } } } } From fa948d7fd592aee3489999bba0cc13fee721a013 Mon Sep 17 00:00:00 2001 From: Leandro Reis Date: Fri, 14 Jun 2024 18:17:21 -0300 Subject: [PATCH 39/43] test: refactor and enhance the entire currency management process - Refactored tests to improve structure and maintainability. - Expanded test coverage to ensure comprehensive validation of the currency management process. - Ensured error handling is properly tested to maintain robustness. --- test/integration/api.test.ts | 67 +++++++++++------------------------- 1 file changed, 21 insertions(+), 46 deletions(-) diff --git a/test/integration/api.test.ts b/test/integration/api.test.ts index 70c3ffb29..cfc18a1b6 100644 --- a/test/integration/api.test.ts +++ b/test/integration/api.test.ts @@ -1,52 +1,15 @@ import axios from 'axios'; describe('Main API', () => { - it('should convert USD to BRL', async () => { - const params = { from: 'USD', to: 'BRL', amount: 10 }; - const response = await axios.get( - 'http://localhost:3000/api/v1/currencies/convert', - { params } - ); - const output = response.data; - expect(output.from).toBe('USD'); - expect(output.to).toBe('BRL'); - expect(output.givenAmount).toBe(10); - expect(output.convertedAmount).toBeGreaterThan(0); - expect(output.convertedAmountFormatted).toContain('R$'); - expect(output.decimalDigits).toBe(2); - expect(output.symbol).toBe('R$'); - }); - - it('should convert BRL to USD', async () => { - const brlResponse = await axios.get( - 'http://localhost:3000/api/v1/currencies/BRL' - ); - const brlOutput = brlResponse.data; - const amount = brlOutput.rate * 2; - const params = { from: 'brl', to: 'usd', amount: amount }; - const response = await axios.get( - 'http://localhost:3000/api/v1/currencies/convert', - { params } - ); - const output = response.data; - expect(output.from).toBe('BRL'); - expect(output.to).toBe('USD'); - expect(output.givenAmount).toBe(amount); - expect(output.convertedAmount).toBe(2.0); - expect(output.convertedAmountFormatted).toContain('$'); - expect(output.decimalDigits).toBe(2); - expect(output.symbol).toBe('$'); - }); - - it('should create, get and delete a currency rate', async () => { + it('should create, get, convert and delete a currency rate', async () => { const payload = { - name: 'Any Currency', - code: 'ANY', - symbol: '$', + name: 'Grand Theft Auto Dollar', + symbol: 'GTA$', + code: 'gta', decimalDigits: 2, - baseAmount: 2, - equivalentCurrencyAmount: 1, - equivalentCurrencyCode: 'USD', + baseAmount: 1250000.0, + equivalentCurrencyCode: 'brl', + equivalentCurrencyAmount: 83.5, }; await axios.post('http://localhost:3000/api/v1/currencies', payload); const getResponse = await axios.get( @@ -54,15 +17,27 @@ describe('Main API', () => { ); const getOutput = getResponse.data; expect(getOutput.name).toEqual(payload.name); - expect(getOutput.code).toEqual(payload.code); + expect(getOutput.code).toEqual(payload.code.toUpperCase()); expect(getOutput.symbol).toEqual(payload.symbol); expect(getOutput.decimalDigits).toEqual(payload.decimalDigits); - expect(getOutput.rate).toEqual(2); + expect(getOutput.rate).toBeDefined(); + const convertParams = { from: 'brl', to: 'gta', amount: 83.5 }; + const convertResponse = await axios.get( + 'http://localhost:3000/api/v1/currencies/convert', + { params: convertParams } + ); + const convertOutput = convertResponse.data; + expect(convertOutput.from).toBe('BRL'); + expect(convertOutput.to).toBe('GTA'); + expect(convertOutput.givenAmount).toBe(83.5); + expect(convertOutput.convertedAmount).toBe(1250000); + expect(convertOutput.convertedAmountFormatted).toContain('GTA$ 1250000.00'); const deleteResponse = await axios.delete( `http://localhost:3000/api/v1/currencies/${getOutput.code}` ); const deleteOutput = deleteResponse.data; expect(deleteOutput).toEqual({ message: 'Currency deleted' }); + expect(deleteResponse.status).toBe(200); }); it('should throw an error when trying to create a currency with invalid name', async () => { From 85b55050587c594baa26a9aae270f43ee5bc537c Mon Sep 17 00:00:00 2001 From: Leandro Reis Date: Fri, 14 Jun 2024 18:51:30 -0300 Subject: [PATCH 40/43] fix: throw CurrencyNotFound when remote gateway throws error 422 --- src/infra/http-client/axios-adapter.ts | 2 +- .../currency-rate-repository-api.ts | 45 +++++++++++-------- 2 files changed, 27 insertions(+), 20 deletions(-) diff --git a/src/infra/http-client/axios-adapter.ts b/src/infra/http-client/axios-adapter.ts index e6b2afba5..0b1223dbc 100644 --- a/src/infra/http-client/axios-adapter.ts +++ b/src/infra/http-client/axios-adapter.ts @@ -1,4 +1,4 @@ -import { HttpClient, Options, Settings } from '@/infra/http-client'; +import HttpClient, { Options, Settings } from '@/infra/http-client/http-client'; import axios from 'axios'; export class AxiosAdapter extends HttpClient { diff --git a/src/infra/repository/currency-rate-repository-api.ts b/src/infra/repository/currency-rate-repository-api.ts index 2212dc885..102336b34 100644 --- a/src/infra/repository/currency-rate-repository-api.ts +++ b/src/infra/repository/currency-rate-repository-api.ts @@ -14,26 +14,33 @@ export class CurrencyRateRepositoryApi implements CurrencyRateRepository { if (this.currencyMap.has(code)) { return this.currencyMap.get(code)!; } - const currencyResponse = await this.currencyGateway.getCurrencies({ - currencies: [code], - }); - const currencyData = currencyResponse.data[code]; - if (!currencyResponse.data[code] || !currencyResponse.data[code]) { - throw new CurrencyNotFoundError(code); + try { + const currencyResponse = await this.currencyGateway.getCurrencies({ + currencies: [code], + }); + const currencyData = currencyResponse.data[code]; + if (!currencyResponse.data[code] || !currencyResponse.data[code]) { + throw new CurrencyNotFoundError(code); + } + const rateResponse = await this.currencyGateway.getRates({ + currencies: [code], + }); + const rateData = rateResponse.data[code]; + const currency = new CurrencyRate( + currencyData.code, + currencyData.name, + currencyData.code, + currencyData.symbol, + currencyData.decimal_digits, + rateData.value + ); + return currency; + } catch (error: any) { + if (error?.message === 'Request failed with status code 422') { + throw new CurrencyNotFoundError(code); + } + throw error; } - const rateResponse = await this.currencyGateway.getRates({ - currencies: [code], - }); - const rateData = rateResponse.data[code]; - const currency = new CurrencyRate( - currencyData.code, - currencyData.name, - currencyData.code, - currencyData.symbol, - currencyData.decimal_digits, - rateData.value - ); - return currency; } async add(currencyRate: CurrencyRate): Promise { From 34a3075e54825a834bc9b37867fc2a60026a3137 Mon Sep 17 00:00:00 2001 From: Leandro Reis Date: Fri, 14 Jun 2024 19:00:48 -0300 Subject: [PATCH 41/43] refactor: Improved script organization and naming for better readability and ease of use. - Renamed files to be more self-explanatory: - main-express-gateway-offline.ts - main-express-gateway-online.ts - main-express-with-fake.ts - main-hapi-with-fake.ts - Updated package.json scripts: - Added `start` script - Added `dev:express+gw-on` script - Renamed existing scripts for consistency and clarity --- package.json | 13 ++--- src/index.ts | 5 -- ...way.ts => main-express-gateway-offline.ts} | 2 - src/main/main-express-gateway-online.ts | 48 +++++++++++++++++++ ...n-express.ts => main-express-with-fake.ts} | 0 .../{main-hapi.ts => main-hapi-with-fake.ts} | 0 src/main/{main-express-pg.ts => main.ts} | 1 - 7 files changed, 55 insertions(+), 14 deletions(-) delete mode 100644 src/index.ts rename src/main/{main-gateway.ts => main-express-gateway-offline.ts} (93%) create mode 100644 src/main/main-express-gateway-online.ts rename src/main/{main-express.ts => main-express-with-fake.ts} (100%) rename src/main/{main-hapi.ts => main-hapi-with-fake.ts} (100%) rename src/main/{main-express-pg.ts => main.ts} (95%) diff --git a/package.json b/package.json index d191865f9..53f661f2e 100644 --- a/package.json +++ b/package.json @@ -6,12 +6,13 @@ "author": "Leandro Reis ", "license": "MIT", "scripts": { - "dev": "ts-node -r tsconfig-paths/register src/index.ts", - "dev:express": "ts-node-dev -r tsconfig-paths/register src/main/main-express.ts", - "dev:hapi": "ts-node-dev -r tsconfig-paths/register src/main/main-hapi.ts", - "dev:express-pg": "ts-node-dev -r tsconfig-paths/register src/main/main-express-pg.ts", - "dev:gateway": "ts-node-dev -r tsconfig-paths/register src/main/main-gateway.ts", - "test": "jest", + "start": "ts-node -r tsconfig-paths/register src/main/main.ts", + "dev": "ts-node-dev -r tsconfig-paths/register src/main/main.ts", + "dev:express+fake": "ts-node-dev -r tsconfig-paths/register src/main/main-express-with-fake.ts", + "dev:express+gw-off": "ts-node-dev -r tsconfig-paths/register src/main/main-express-gateway-offline.ts", + "dev:express+gw-on": "ts-node-dev -r tsconfig-paths/register src/main/main-express-gateway-online.ts", + "dev:hapi+fake": "ts-node-dev -r tsconfig-paths/register src/main/main-hapi-with-fake.ts", + "test": "jest --detectOpenHandles", "test:watch": "jest --watch --detectOpenHandles" }, "dependencies": { diff --git a/src/index.ts b/src/index.ts deleted file mode 100644 index bf9e730fe..000000000 --- a/src/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -async function main() { - // This is the entry point of the application -} - -main().catch(console.error); diff --git a/src/main/main-gateway.ts b/src/main/main-express-gateway-offline.ts similarity index 93% rename from src/main/main-gateway.ts rename to src/main/main-express-gateway-offline.ts index e17b06a9e..f2b022c4d 100644 --- a/src/main/main-gateway.ts +++ b/src/main/main-express-gateway-offline.ts @@ -4,7 +4,6 @@ import { DeleteCurrencyRateByCode, GetCurrencyRateByCode, } from '@/application/use-case/'; -// import { CurrenciesApiGatewayOnline } from '@/infra/gateway'; import { CurrenciesApiGatewayStatic } from '@/infra/gateway'; import { ConvertCurrencyController, @@ -17,7 +16,6 @@ import { import { CurrencyRateRepositoryApi } from '@/infra/repository/'; const httpServer = new ExpressAdapter(); -// const httpClient = new AxiosAdapter(); const currencyGateway = new CurrenciesApiGatewayStatic(); const currencyRateRepository = new CurrencyRateRepositoryApi(currencyGateway); const createCurrencyRate = new CreateCurrencyRate(currencyRateRepository); diff --git a/src/main/main-express-gateway-online.ts b/src/main/main-express-gateway-online.ts new file mode 100644 index 000000000..c734f436c --- /dev/null +++ b/src/main/main-express-gateway-online.ts @@ -0,0 +1,48 @@ +import { + ConvertCurrency, + CreateCurrencyRate, + DeleteCurrencyRateByCode, + GetCurrencyRateByCode, +} from '@/application/use-case/'; +import { CurrenciesApiGatewayOnline } from '@/infra/gateway'; +import { AxiosAdapter } from '@/infra/http-client'; +import { + ConvertCurrencyController, + CreateCurrencyController, + CurrenciesRoutes, + DeleteCurrencyController, + ExpressAdapter, + GetCurrencyByCodeController, +} from '@/infra/http-server/'; +import { CurrencyRateRepositoryApi } from '@/infra/repository/'; + +const httpServer = new ExpressAdapter(); +const httpClient = new AxiosAdapter(); +const currencyGateway = new CurrenciesApiGatewayOnline(httpClient); +const currencyRateRepository = new CurrencyRateRepositoryApi(currencyGateway); +const createCurrencyRate = new CreateCurrencyRate(currencyRateRepository); +const convertCurrency = new ConvertCurrency(currencyRateRepository); +const getCurrencyByCode = new GetCurrencyRateByCode(currencyRateRepository); +const deleteCurrencyRateByCode = new DeleteCurrencyRateByCode( + currencyRateRepository +); +const createCurrencyController = new CreateCurrencyController( + createCurrencyRate +); +const convertCurrencyController = new ConvertCurrencyController( + convertCurrency +); +const getCurrencyByCodeController = new GetCurrencyByCodeController( + getCurrencyByCode +); +const deleteCurrencyController = new DeleteCurrencyController( + deleteCurrencyRateByCode +); +const currencyRoutes = new CurrenciesRoutes({ + createCurrencyController, + convertCurrencyController, + getCurrencyByCodeController, + deleteCurrencyController, +}); +currencyRoutes.registerRoutes(httpServer); +httpServer.listen(3000); diff --git a/src/main/main-express.ts b/src/main/main-express-with-fake.ts similarity index 100% rename from src/main/main-express.ts rename to src/main/main-express-with-fake.ts diff --git a/src/main/main-hapi.ts b/src/main/main-hapi-with-fake.ts similarity index 100% rename from src/main/main-hapi.ts rename to src/main/main-hapi-with-fake.ts diff --git a/src/main/main-express-pg.ts b/src/main/main.ts similarity index 95% rename from src/main/main-express-pg.ts rename to src/main/main.ts index 1814651d8..e1a1833ca 100644 --- a/src/main/main-express-pg.ts +++ b/src/main/main.ts @@ -23,7 +23,6 @@ const listCurrencies = new ListCurrenciesQuery(connection); const currencyRateRepository = new CurrencyRateRepositoryDatabase(connection); const createCurrencyRate = new CreateCurrencyRate(currencyRateRepository); const convertCurrency = new ConvertCurrency(currencyRateRepository); -// const getCurrencyRateByCode = new GetCurrencyRateByCode(currencyRateRepository); const getCurrencyByCodeQuery = new GetCurrencyByCodeQuery(connection); const deleteCurrencyRateByCode = new DeleteCurrencyRateByCode( currencyRateRepository From e926ae6c520fd6a515352ab6f3e40901e230d150 Mon Sep 17 00:00:00 2001 From: Leandro Reis Date: Fri, 14 Jun 2024 21:41:16 -0300 Subject: [PATCH 42/43] chore: configure Docker and environment variables - Added Dockerfile with appropriate configuration for building and running the app. - Updated docker-compose.yml to include the app service and configure environment variables. - Installed and configured dotenv for managing environment variables. - Updated main*.ts files to use environment variables for database connection strings. - Updated gateway online configuration to utilize environment variables. - Modified database connection logic to use environment variables for connection string. These changes ensure the application can be built and run in a Docker environment with proper environment variable management for configuration consistency. --- .dockerignore | 9 ++++++++ .env | 3 +++ .gitignore | 3 ++- Dockerfile | 13 ++++++++++++ docker-compose.yml | 16 ++++++++++++++ package.json | 1 + src/env.ts | 21 +++++++++++++++++++ src/infra/database/pgpromise-adapter.ts | 3 ++- .../gateway/currency-api-gateway-online.ts | 3 ++- src/main/main-express-gateway-offline.ts | 3 ++- src/main/main-express-gateway-online.ts | 3 ++- src/main/main-express-with-fake.ts | 3 ++- src/main/main-hapi-with-fake.ts | 3 ++- src/main/main.ts | 3 ++- yarn.lock | 5 +++++ 15 files changed, 84 insertions(+), 8 deletions(-) create mode 100644 .dockerignore create mode 100644 .env create mode 100644 Dockerfile create mode 100644 src/env.ts diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 000000000..691304456 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,9 @@ +coverage +initdb +node_modules +dist +.build +Dockerfile +docker-compose.yml +.git +.gitignore \ No newline at end of file diff --git a/.env b/.env new file mode 100644 index 000000000..8cbc0bb4c --- /dev/null +++ b/.env @@ -0,0 +1,3 @@ +HTTP_SERVER_PORT=3000 +DATABASE_URL=postgres://postgres:123456@localhost:5432 +CURRENCYAPI_API_KEY=cur_live_cuO1rc3J9p5XJE9QYRgyadj3ERuP57GKFUdt9yOh \ No newline at end of file diff --git a/.gitignore b/.gitignore index 6d3a831e2..b43123061 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ -node_modules coverage +node_modules +dist .DS_Store \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 000000000..5290fc800 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,13 @@ +FROM node:20.14.0 + +WORKDIR /usr/src/app + +COPY package.json yarn.lock ./ + +RUN yarn install + +COPY . . + +EXPOSE 3000 + +CMD ["yarn", "start"] \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index 02a412e7e..a1d80f216 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,6 +1,22 @@ version: '3.9' services: + app: + build: . + ports: + - '3000:3000' + environment: + NODE_ENV: development + HTTP_SERVER_PORT: 3000 + DATABASE_URL: postgres://postgres:123456@db:5432 + CURRENCYAPI_API_KEY: cur_live_cuO1rc3J9p5XJE9QYRgyadj3ERuP57GKFUdt9yOh + volumes: + - .:/usr/src/app + - /usr/src/app/node_modules + command: yarn dev + depends_on: + - db + db: image: postgres ports: diff --git a/package.json b/package.json index 53f661f2e..af331312a 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,7 @@ "axios": "^1.7.2", "crypto": "^1.0.1", "currency.js": "^2.0.4", + "dotenv": "^16.4.5", "express": "^4.19.2", "pg-promise": "^11.8.0", "swagger-ui-express": "^5.0.1" diff --git a/src/env.ts b/src/env.ts new file mode 100644 index 000000000..01428cc8f --- /dev/null +++ b/src/env.ts @@ -0,0 +1,21 @@ +import dotenv from 'dotenv'; + +dotenv.config(); + +const HTTP_SERVER_PORT = Number(process.env.HTTP_SERVER_PORT || 3000); +const DATABASE_URL = process.env.DATABASE_URL; +if (!DATABASE_URL) { + throw new Error('DATABASE_URL is not set'); +} +const CURRENCYAPI_API_KEY = process.env.CURRENCYAPI_API_KEY; +if (!CURRENCYAPI_API_KEY) { + throw new Error('CURRENCYAPI_API_KEY is not set'); +} + +export const env = { + HTTP_SERVER_PORT, + DATABASE_URL, + CURRENCYAPI_API_KEY, +}; + +export default env; diff --git a/src/infra/database/pgpromise-adapter.ts b/src/infra/database/pgpromise-adapter.ts index dd21922a0..37030b29d 100644 --- a/src/infra/database/pgpromise-adapter.ts +++ b/src/infra/database/pgpromise-adapter.ts @@ -1,3 +1,4 @@ +import env from '@/env'; import { DatabaseConnection } from '@/infra/database'; import pgp from 'pg-promise'; @@ -6,7 +7,7 @@ export class PgPromiseAdapter implements DatabaseConnection { connection: any; constructor() { - this.connection = pgp()('postgres://postgres:123456@localhost:5432'); + this.connection = pgp()(env.DATABASE_URL); } query(statement: string, params: any): Promise { diff --git a/src/infra/gateway/currency-api-gateway-online.ts b/src/infra/gateway/currency-api-gateway-online.ts index 4dad80086..eacc7ada1 100644 --- a/src/infra/gateway/currency-api-gateway-online.ts +++ b/src/infra/gateway/currency-api-gateway-online.ts @@ -1,3 +1,4 @@ +import env from '@/env'; import { CurrenciesResponse, CurrencyApiGateway, @@ -11,7 +12,7 @@ export class CurrenciesApiGatewayOnline implements CurrencyApiGateway { httpClient.setSettings({ baseURL: 'https://api.currencyapi.com/v3/', headers: { - apiKey: 'cur_live_cuO1rc3J9p5XJE9QYRgyadj3ERuP57GKFUdt9yOh', + apiKey: env.CURRENCYAPI_API_KEY, }, }); } diff --git a/src/main/main-express-gateway-offline.ts b/src/main/main-express-gateway-offline.ts index f2b022c4d..f7225430c 100644 --- a/src/main/main-express-gateway-offline.ts +++ b/src/main/main-express-gateway-offline.ts @@ -4,6 +4,7 @@ import { DeleteCurrencyRateByCode, GetCurrencyRateByCode, } from '@/application/use-case/'; +import env from '@/env'; import { CurrenciesApiGatewayStatic } from '@/infra/gateway'; import { ConvertCurrencyController, @@ -43,4 +44,4 @@ const currencyRoutes = new CurrenciesRoutes({ deleteCurrencyController, }); currencyRoutes.registerRoutes(httpServer); -httpServer.listen(3000); +httpServer.listen(env.HTTP_SERVER_PORT); diff --git a/src/main/main-express-gateway-online.ts b/src/main/main-express-gateway-online.ts index c734f436c..0d15ac2c4 100644 --- a/src/main/main-express-gateway-online.ts +++ b/src/main/main-express-gateway-online.ts @@ -4,6 +4,7 @@ import { DeleteCurrencyRateByCode, GetCurrencyRateByCode, } from '@/application/use-case/'; +import env from '@/env'; import { CurrenciesApiGatewayOnline } from '@/infra/gateway'; import { AxiosAdapter } from '@/infra/http-client'; import { @@ -45,4 +46,4 @@ const currencyRoutes = new CurrenciesRoutes({ deleteCurrencyController, }); currencyRoutes.registerRoutes(httpServer); -httpServer.listen(3000); +httpServer.listen(env.HTTP_SERVER_PORT); diff --git a/src/main/main-express-with-fake.ts b/src/main/main-express-with-fake.ts index 4d88acde4..644f842e5 100644 --- a/src/main/main-express-with-fake.ts +++ b/src/main/main-express-with-fake.ts @@ -4,6 +4,7 @@ import { DeleteCurrencyRateByCode, GetCurrencyRateByCode, } from '@/application/use-case/'; +import env from '@/env'; import { ConvertCurrencyController, CreateCurrencyController, @@ -41,4 +42,4 @@ const currencyRoutes = new CurrenciesRoutes({ deleteCurrencyController, }); currencyRoutes.registerRoutes(httpServer); -httpServer.listen(3000); +httpServer.listen(env.HTTP_SERVER_PORT); diff --git a/src/main/main-hapi-with-fake.ts b/src/main/main-hapi-with-fake.ts index e78d2b53a..138185207 100644 --- a/src/main/main-hapi-with-fake.ts +++ b/src/main/main-hapi-with-fake.ts @@ -4,6 +4,7 @@ import { DeleteCurrencyRateByCode, GetCurrencyRateByCode, } from '@/application/use-case/'; +import env from '@/env'; import { ConvertCurrencyController, CreateCurrencyController, @@ -41,4 +42,4 @@ const currencyRoutes = new CurrenciesRoutes({ deleteCurrencyController, }); currencyRoutes.registerRoutes(httpServer); -httpServer.listen(3000); +httpServer.listen(env.HTTP_SERVER_PORT); diff --git a/src/main/main.ts b/src/main/main.ts index e1a1833ca..f0bafaddb 100644 --- a/src/main/main.ts +++ b/src/main/main.ts @@ -5,6 +5,7 @@ import { CreateCurrencyRate, DeleteCurrencyRateByCode, } from '@/application/use-case/'; +import env from '@/env'; import { PgPromiseAdapter } from '@/infra/database/'; import { ConvertCurrencyController, @@ -48,4 +49,4 @@ const currencyRoutes = new CurrenciesRoutes({ deleteCurrencyController, }); currencyRoutes.registerRoutes(httpServer); -httpServer.listen(3000); +httpServer.listen(env.HTTP_SERVER_PORT); diff --git a/yarn.lock b/yarn.lock index 1ad6c1393..5af83d17a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1721,6 +1721,11 @@ dir-glob@^3.0.1: dependencies: path-type "^4.0.0" +dotenv@^16.4.5: + version "16.4.5" + resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.4.5.tgz#cdd3b3b604cb327e286b4762e13502f717cb099f" + integrity sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg== + dynamic-dedupe@^0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/dynamic-dedupe/-/dynamic-dedupe-0.3.0.tgz#06e44c223f5e4e94d78ef9db23a6515ce2f962a1" From 772b06756388ec8bc7c0038a786634ec56e48a15 Mon Sep 17 00:00:00 2001 From: Leandro Reis Date: Sat, 15 Jun 2024 00:57:01 -0300 Subject: [PATCH 43/43] doc: rename the readmes to challenge-README*.md and create a new README with the challenge implementation information --- README.md | 271 +++++++++++++++++++------ challenge-README.md | 82 ++++++++ README.pt.md => challenge-README.pt.md | 0 3 files changed, 296 insertions(+), 57 deletions(-) create mode 100644 challenge-README.md rename README.pt.md => challenge-README.pt.md (100%) diff --git a/README.md b/README.md index 22af01577..25bef45ee 100644 --- a/README.md +++ b/README.md @@ -1,82 +1,239 @@ # Hurb Bravo Challenge -[[English](README.md) | [Portuguese](README.pt.md)] +## Descrição + +Esta é uma API para conversão monetária. Ela suporta conversões entre diversas moedas, incluindo moedas fiduciárias, criptomoedas e moedas fictícias. A moeda de lastro utilizada é o USD, e as cotações foram obtidas de APIs públicas para garantir que sejam reais e atuais. + +A solução foi desenvolvida utilizando, TDD, DDD, Ports & Adapters, Clean Architecture, design-patterns e outras estratégias arquiteturais. Sei que essa arquitetura pode ser considerada demasiadamente robusta para um projeto tão pequeno, mas o intuito é mostrar o que tenho em minha "caixa de ferramentas" e também dar a vocês material de análise de minha forma de codificar. No dia a dia gosto de dimensionar a solução de software para alcançar o melhor proveito no negócio. + +## Tecnologias Utilizadas + +- Node.js +- TypeScript +- Express +- Hapi +- Swagger UI +- PostgreSQL +- Docker +- Jest + +## Endpoints + +Indico fortemente a utilização do swagger para consultar a documentação da API e também para executar as requests. Essa documentação pode ser acessada no endereço `http://locahost:3000/api-docs`, mas caso queira ou precise testar utilizando uma ferramenta de requisições http como Postman, o endereço base para as requisições é o `http://localhost:3000/api/v1`. + +### Lista Paginada de Moedas + +Este endpoint não foi solicitado no desafio mas decidi fazer para ter um bom exemplo da utilização de CQRS. Eu fiz a _query_ do _[ GET ] /currencies/{code}_ também mas ela sozinha não realça as vantagens da separação de comandos e consultas. É importante também mencionar que este endpoint apenas estará disponível se o projeto for inicializado com `yarn start` ou `yarn dev`. O único objetivo desse comportamento é mostrar que as rotas também são configuráveis sem afetar a aplicação. + +`[ GET ] /currencies` + +```json +// query +{ + "currencies": [], // Código das moedas a serem filtradas + "page": 1, // Página corrente + "perPage": 10, // Quantidade de registros por página + "sortBy": "name", // Campo pelo qual a consulta será ordenada + "sortOrder": "asc", // Direção da ordenação +} +``` + +```json +// response schema +{ + "meta": { + "page": 0, + "perPage": 0, + "pageCount": 0, + "totalCount": 0 + }, + "data": [ + { + "id": "string", + "name": "string", + "code": "string", + "symbol": "string", + "decimalDigits": 0, + "rate": 0.1 + } + ] +} +``` + +### Adiciona uma nova moeda + +O desafio não define ou esclarece quem utilizará essa API, então pensando em praticidade, ao invés de pedir o _rate_ (valor proporcional de uma moeda em relação à moeda de lastro), que resultaria em um cadastro simples, eu preferi solicitar o valor da nova moeda bem como de uma moeda equivalente. Podemos inclusive seguir o exemplo citado no desafio do cadastro de uma moeda fictícia do GTA onde \$ 1.250.000,00 no jogo custam R$ 83,50 no mundo real. Com isso calculamos o valor proporcional da moeda em dólar. + +`[ POST ] /currencies` + +```json +// body +{ + "name": "Grand Theft Auto Dollar", + "code": "GTA", + "symbol": "GTA$", + "baseAmount": 1250000.00, + "equivalentCurrencyAmount": 83.50, + "equivalentCurrencyCode": "BRL" +} +``` + +```json +// response schema +{ + "currencyId": "string" +} +``` + +### Converte uma moeda em outra + +A descrição do desafio propõe a API de conversão de moeda mas não diz como deve ser seu retorno. Por isso tomei a liberdade de devolver um objeto com algumas informações que julguei serem uteis em uma API real de conversão. + +`[ POST ] /currencies/convert` + +```json +// query +{ + "from": "string", + "to": "string", + "amount": 0.1, +} +``` + +```json +// response schema +{ + "from": "string", + "to": "string", + "givenAmount": 0.1, + "convertedAmount": 0.1, + "convertedAmountFormatted": "string", + "decimalDigits": 0, + "symbol": "string" +} +``` + +### Recupera uma moeda específica + +Esse endpoint também não foi solicitado no desafio mas ele é super importante nos testes automatizado garantido que as moedas criadas na aplicação foram criadas corretamente. + +`[ GET ] /currencies/{code}` + +```json +// param +{ + "code": "string", +} +``` + +```json +// response schema +{ + "id": "string", + "name": "string", + "code": "string", + "symbol": "string", + "decimalDigits": 0, + "rate": 0.1 +} +``` + +### Exclui uma moeda do repositório + +`[ DELETE ] /currencies/{code}` + +Todas as configurações dessa aplicação (veja mais [aqui](#ancora1)) oferecem a possibilidade de criar e remover moedas, mas no caso das configurações que utilizam o gateway externo, apenas as novas moedas podem ser removidas. + +```json +// param +{ + "code": "string", +} +``` -Build an API, which responds to JSON, for currency conversion. It must have a backing currency (USD) and make conversions between different currencies with **real and live values**. +```json +// response schema +{ + "message": "string", +} +``` -The API must convert between the following currencies: -- USD -- BRL -- EUR -- BTC -- ETH +## Como Executar -Other coins could be added as usage. +### Pré-requisitos -Ex: USD to BRL, USD to BTC, ETH to BRL, etc... +- Docker & Docker Compose (caso queira rodar com o banco de dados postgres) +- Node +- Yarn (opcional) -The request must receive as parameters: The source currency, the amount to be converted and the final currency. +### Preparação do repositório -Ex: `?from=BTC&to=EUR&amount=123.45` +1. Clone o repositório: -Also build an endpoint to add and remove API supported currencies using HTTP verbs. +```shell +$ git clone https://github.com/hurbcom/challenge-bravo.git +``` -The API must support conversion between FIAT, crypto and fictitious. Example: BRL->HURB, HURB->ETH +2. Navegue até o diretório do projeto: -"Currency is the means by which monetary transactions are effected." (Wikipedia, 2021). +```shell +$ cd challenge-bravo +``` -Therefore, it is possible to imagine that new coins come into existence or cease to exist, it is also possible to imagine fictitious coins such as Dungeons & Dragons coins being used in these transactions, such as how much is a Gold Piece (Dungeons & Dragons) in Real or how much is the GTA$1 in Real. +3. Acesse a branch que criei com a solução do desafio: -Let's consider the PSN quote where GTA$1,250,000.00 cost R$83.50 we clearly have a relationship between the currencies, so it is possible to create a quote. (Playstation Store, 2021). +```shell +$ git checkout 'leandro/challenge-implementation' +``` -Ref: -Wikipedia [Institutional Website]. Available at: . Accessed on: 28 April 2021. -Playstation Store [Virtual Store]. Available at: . Accessed on: 28 April 2021. +### Passos para rodar a aplicação no Docker -You can use any programming language for the challenge. Below is the list of languages ​​that we here at Hurb have more affinity: +1. Construa e inicie os containers Docker: -- JavaScript (NodeJS) -- Python -- Go -- Ruby -- C++ -- PHP +```shell +$ docker-compose build +... +$ docker-compose up +``` -## Requirements +* _Para parar a aplicação execute ctrl+D no terminal_ +* _Para parar o container execute `docker-compose down`_ -- Fork this challenge and create your project (or workspace) using your version of that repository, as soon as you finish the challenge, submit a _pull request_. - - If you have any reason not to submit a _pull request_, create a private repository on Github, do every challenge on the **main** branch and don't forget to fill in the `pull-request.txt` file. As soon as you finish your development, add the user `automator-hurb` to your repository as a contributor and make it available for at least 30 days. **Do not add the `automator-hurb` until development is complete.** - - If you have any problem creating the private repository, at the end of the challenge fill in the file called `pull-request.txt`, compress the project folder - including the `.git` folder - and send it to us by email. -- The code needs to run on macOS or Ubuntu (preferably as a Docker container) -- To run your code, all you need to do is run the following commands: - - git clone \$your-fork - - cd \$your-fork - - command to install dependencies - - command to run the application -- The API can be written with or without the help of _frameworks_ - - If you choose to use a _framework_ that results in _boilerplate code_, mark in the README which piece of code was written by you. The more code you make, the more content we will have to rate. -- The API needs to support a volume of 1000 requests per second in a stress test. -- The API needs to include real and current quotes through integration with public currency quote APIs +### Passos para rodar a aplicação localmente -## Evaluation criteria +1. Instale as dependências -- **Organization of code**: Separation of modules, view and model, back-end and front-end -- **Clarity**: Does the README explain briefly what the problem is and how can I run the application? -- **Assertiveness**: Is the application doing what is expected? If something is missing, does the README explain why? -- **Code readability** (including comments) -- **Security**: Are there any clear vulnerabilities? -- **Test coverage** (We don't expect full coverage) -- **History of commits** (structure and quality) -- **UX**: Is the interface user-friendly and self-explanatory? Is the API intuitive? -- **Technical choices**: Is the choice of libraries, database, architecture, etc. the best choice for the application? +```sh +$ yarn install +``` -## Doubts +2. Inicie o banco de dados via docker-compose -Any questions you may have, check the [_issues_](https://github.com/HurbCom/challenge-bravo/issues) to see if someone hasn't already and if you can't find your answer, open one yourself. new issue! +```sh +$ docker-compose up db +``` -Godspeed! ;) +1. Inicie a aplicação -

- Challange accepted -

+```sh +$ yarn start +``` + +* _Para parar a aplicação execute ctrl+D no terminal_ + + + +> Há ainda outros scripts de inicialização que podem ser utilizados. Cada um sobe o servidor com uma configuração diferente: +> - `start` ou `dev`: Inicia o servidor express com swagger UI e repositório postgres que roda no docker; +> - `dev:express+fake`: Iniciar o servidor express com swagger UI e Repositório em memória; +> - `dev:hapi+fake`: Inicia o servidor hapi com repositório em memória; +> - `dev:express+gw-on`: Inicia o servidor express com swagger UI e o repositório utiliza o gateway currencyapi.com para ter a contação do dia de forma online. +> - `dev:express+gw-off`: Inicia o servidor express com swagger UI e simula a requisição para o gatewayapi.com (para fins de testes sem fazer requisições reais pois são liberadas apenas 300 requisições/mês) + +### Executar testes + +```sh +$ yarn test +``` +- Também pode ser utilizado o `yarn test:watch` que roda os testes com a flag `--watch`. \ No newline at end of file diff --git a/challenge-README.md b/challenge-README.md new file mode 100644 index 000000000..22af01577 --- /dev/null +++ b/challenge-README.md @@ -0,0 +1,82 @@ +# Hurb Bravo Challenge + +[[English](README.md) | [Portuguese](README.pt.md)] + +Build an API, which responds to JSON, for currency conversion. It must have a backing currency (USD) and make conversions between different currencies with **real and live values**. + +The API must convert between the following currencies: + +- USD +- BRL +- EUR +- BTC +- ETH + +Other coins could be added as usage. + +Ex: USD to BRL, USD to BTC, ETH to BRL, etc... + +The request must receive as parameters: The source currency, the amount to be converted and the final currency. + +Ex: `?from=BTC&to=EUR&amount=123.45` + +Also build an endpoint to add and remove API supported currencies using HTTP verbs. + +The API must support conversion between FIAT, crypto and fictitious. Example: BRL->HURB, HURB->ETH + +"Currency is the means by which monetary transactions are effected." (Wikipedia, 2021). + +Therefore, it is possible to imagine that new coins come into existence or cease to exist, it is also possible to imagine fictitious coins such as Dungeons & Dragons coins being used in these transactions, such as how much is a Gold Piece (Dungeons & Dragons) in Real or how much is the GTA$1 in Real. + +Let's consider the PSN quote where GTA$1,250,000.00 cost R$83.50 we clearly have a relationship between the currencies, so it is possible to create a quote. (Playstation Store, 2021). + +Ref: +Wikipedia [Institutional Website]. Available at: . Accessed on: 28 April 2021. +Playstation Store [Virtual Store]. Available at: . Accessed on: 28 April 2021. + +You can use any programming language for the challenge. Below is the list of languages ​​that we here at Hurb have more affinity: + +- JavaScript (NodeJS) +- Python +- Go +- Ruby +- C++ +- PHP + +## Requirements + +- Fork this challenge and create your project (or workspace) using your version of that repository, as soon as you finish the challenge, submit a _pull request_. + - If you have any reason not to submit a _pull request_, create a private repository on Github, do every challenge on the **main** branch and don't forget to fill in the `pull-request.txt` file. As soon as you finish your development, add the user `automator-hurb` to your repository as a contributor and make it available for at least 30 days. **Do not add the `automator-hurb` until development is complete.** + - If you have any problem creating the private repository, at the end of the challenge fill in the file called `pull-request.txt`, compress the project folder - including the `.git` folder - and send it to us by email. +- The code needs to run on macOS or Ubuntu (preferably as a Docker container) +- To run your code, all you need to do is run the following commands: + - git clone \$your-fork + - cd \$your-fork + - command to install dependencies + - command to run the application +- The API can be written with or without the help of _frameworks_ + - If you choose to use a _framework_ that results in _boilerplate code_, mark in the README which piece of code was written by you. The more code you make, the more content we will have to rate. +- The API needs to support a volume of 1000 requests per second in a stress test. +- The API needs to include real and current quotes through integration with public currency quote APIs + +## Evaluation criteria + +- **Organization of code**: Separation of modules, view and model, back-end and front-end +- **Clarity**: Does the README explain briefly what the problem is and how can I run the application? +- **Assertiveness**: Is the application doing what is expected? If something is missing, does the README explain why? +- **Code readability** (including comments) +- **Security**: Are there any clear vulnerabilities? +- **Test coverage** (We don't expect full coverage) +- **History of commits** (structure and quality) +- **UX**: Is the interface user-friendly and self-explanatory? Is the API intuitive? +- **Technical choices**: Is the choice of libraries, database, architecture, etc. the best choice for the application? + +## Doubts + +Any questions you may have, check the [_issues_](https://github.com/HurbCom/challenge-bravo/issues) to see if someone hasn't already and if you can't find your answer, open one yourself. new issue! + +Godspeed! ;) + +

+ Challange accepted +

diff --git a/README.pt.md b/challenge-README.pt.md similarity index 100% rename from README.pt.md rename to challenge-README.pt.md