Compare commits

...
200 Commits
Author SHA1 Message Date
Chris Troutner 58f24954ca Merge pull request #79 from Permissionless-Software-Foundation/ct-unstable
Syncing with upstream ipfs-service-provider
2026-02-16 08:52:14 -07:00
Chris Troutner 6f4f21b373 Fixing npm ci 2026-02-16 08:49:17 -07:00
Chris Troutner b0e42bb956 fixing merge conflicts 2026-02-16 08:43:01 -07:00
Chris Troutner efb02a4381 fix(helia-coord): Updating to v2.0.2 2026-02-16 08:41:49 -07:00
Chris Troutner d894c1e596 Merge pull request #186 from Permissionless-Software-Foundation/ct-unstable
Helia v2.x
2026-02-11 13:27:29 -07:00
Chris Troutner 821cf4abe8 feat(helia-coord): Refactoring to use new v2.x helia-coord with breaking changes 2026-02-11 13:23:57 -07:00
Chris Troutner 74696cfb9e Updated to latest helia-coord v2 version 2026-02-11 11:41:26 -07:00
Chris Troutner 0bfb84e21b Merge pull request #185 from Permissionless-Software-Foundation/ct-unstable
fix(helia-coord): Updating to v1.10.0 with DHT
2026-02-10 17:22:54 -07:00
Chris Troutner 35b4e7079d fix(helia-coord): Updating to v1.10.0 with DHT 2026-02-10 17:19:13 -07:00
Chris Troutner 7d369859a5 Fixing merge conflicts 2026-02-03 15:32:50 -07:00
Chris Troutner 75ea6ecdde Merge pull request #184 from Permissionless-Software-Foundation/ct-unstable
fix(Docker): Updating to Ubuntu 24 & node.js v22
2026-02-03 14:07:59 -07:00
Chris Troutner d4c9665c16 fix(Docker): Updating to Ubuntu 24 & node.js v22 2026-02-03 14:05:15 -07:00
Chris Troutner 768bfdb4e7 Merge pull request #183 from Permissionless-Software-Foundation/ct-unstable
Updating dependencies
2026-02-02 18:56:13 -07:00
Chris Troutner 764fe928d2 fix(minimal-slp-wallet): Updating to v7.1.4 2026-02-02 18:54:18 -07:00
Chris Troutner 2de61458d3 fix(deps): Updating dependencies 2026-02-02 18:41:50 -07:00
Chris Troutner d38e371a05 Merge pull request #182 from Permissionless-Software-Foundation/ct-unstable
fix(package.json): Fixing dependency issues with overrides
2026-02-02 18:27:27 -07:00
Chris Troutner a68ca92468 fix(package.json): Fixing dependency issues with overrides 2026-02-02 18:25:18 -07:00
Chris Troutner 7c26e33a8c Merge pull request #181 from Permissionless-Software-Foundation/ct-unstable
fix(util): Fixing problem with updateUser.js script
2025-10-25 14:33:12 -07:00
Chris Troutner 2595ee8295 fix(util): Fixing problem with updateUser.js script 2025-10-25 14:31:33 -07:00
Chris Troutner 476fdcd0aa Merge pull request #180 from Permissionless-Software-Foundation/ct-unstable
fix(utils): Adding script to update users password or convert to admin
2025-10-25 14:24:43 -07:00
Chris Troutner b6762b215e fix(utils): Adding script to update users password or convert to admin 2025-10-25 14:23:28 -07:00
Chris Troutner 8faadfc5c1 Merge pull request #78 from Permissionless-Software-Foundation/ct-unstable
fix(ipfs-coord): Reducing screen spam when waiting to connect to pref…
2025-10-16 12:47:41 -07:00
Chris Troutner fe0ad56395 fix(ipfs-coord): Reducing screen spam when waiting to connect to preferred service provider 2025-10-16 12:45:01 -07:00
Chris Troutner 0e6a411012 Merge pull request #179 from Permissionless-Software-Foundation/ct-unstable
fix(IPFS): Adding API docs for the IPFS endpoints that need it
2025-10-04 08:32:55 -07:00
Chris Troutner 55e9e35c1f fix(IPFS): Adding API docs for the IPFS endpoints that need it 2025-10-04 08:31:17 -07:00
Chris Troutner 5a05a95876 Merge pull request #77 from Permissionless-Software-Foundation/ct-unstable
fix(docker): Using new CONSUMER_ENV
2025-09-29 13:33:15 -07:00
Chris Troutner 163e5f5f10 fix(docker): Using new CONSUMER_ENV 2025-09-29 13:32:43 -07:00
Chris Troutner ff371a79b9 Merge pull request #76 from Permissionless-Software-Foundation/ct-unstable
fix(reboot): Auto-reboot every 12 hours, to fix connectivity issues
2025-09-29 13:24:21 -07:00
Chris Troutner 487b72bfa2 fix(reboot): Auto-reboot every 12 hours, to fix connectivity issues 2025-09-29 13:22:39 -07:00
Chris Troutner 7d5715be39 Merge pull request #75 from Permissionless-Software-Foundation/dh-test-coverage
feat(tests): Increased unit test coverage
2025-09-24 15:49:50 -07:00
Daniel Gonzalez fc99cafb1d Added more tests 2025-09-24 17:22:05 -04:00
Daniel Gonzalez 0e51dc7f15 Improved adapters unit tests 2025-09-23 16:34:09 -04:00
Daniel Gonzalez c0e0e42fc6 Enviroments fix 2025-09-23 16:33:16 -04:00
Chris Troutner 932110698c Merge pull request #74 from Permissionless-Software-Foundation/ct-unstable
Syncing with upstream
2025-08-21 08:48:27 -07:00
Chris Troutner 59ed8d9e7a Merge branch 'master' into ct-unstable 2025-08-21 08:46:09 -07:00
Chris Troutner 883f9aca67 linting 2025-08-21 08:45:28 -07:00
Chris Troutner 9b88d7f40c Fixing merge conflict and broken test 2025-08-21 08:45:18 -07:00
Chris Troutner 18ea03a4d6 Merge pull request #178 from Permissionless-Software-Foundation/dh-test-coverage
feat(tests): Increased unit test coverage
2025-06-28 07:28:04 -07:00
Daniel Gonzalez 3d911d1062 feat(tests): Increased unit test coverage 2025-06-27 20:11:27 -04:00
Chris Troutner 6ceec71dd6 Merge pull request #73 from Permissionless-Software-Foundation/ct-unstable
Syncing with upstream. Usage improvements.
2025-06-05 18:42:47 -07:00
Chris Troutner f1296fafd6 fix(upstream): Syncing with upstream and fixing merge conflicts 2025-06-05 18:40:55 -07:00
Chris Troutner 479299d747 fix(usage): Removing debugging logs 2025-06-05 18:30:09 -07:00
Chris Troutner cf88fadfdc Merge pull request #177 from Permissionless-Software-Foundation/ct-unstable
Usage: back up to DB to track usage over restarts.
2025-06-05 18:27:06 -07:00
Chris Troutner 4977ae7003 Increasing backup to 10 minutes 2025-06-05 18:25:45 -07:00
Chris Troutner f0f6970a08 Added timer controllers for managing usage stats 2025-06-05 18:25:10 -07:00
Chris Troutner fcb4acb11d feat(usage-use-cases.js): Added functions for persisting usage data to database 2025-06-04 12:26:03 -07:00
Chris Troutner 28e012feb6 Merge pull request #176 from Permissionless-Software-Foundation/e2e-tests
Separating E2E tests from Unit tests
2025-05-30 17:18:40 -07:00
Chris Troutner 958c637c24 Commenting out failing test 2025-05-30 17:17:22 -07:00
Daniel Gonzalez 34381e6e93 Improved e2e tests 2025-05-30 11:53:57 -04:00
Daniel Gonzalez 8843b3e2b0 Separate e2e tests 2025-05-28 20:52:25 -04:00
Chris Troutner 3f91653d23 feat(e2e): separating e2e from unit tests 2025-05-23 07:26:51 -07:00
Chris Troutner 70da4d8000 fix(getTransaction): Reducing debug logs 2025-05-20 06:03:32 -07:00
Chris Troutner 3a4569c33f Merge pull request #175 from Permissionless-Software-Foundation/dh-env-var-admin
feat(admin): added env var setting so only admin can create new users
2025-05-19 13:06:40 -07:00
Daniel Gonzalez 05a0620663 feat(admin): added env var setting so only admin can create new users 2025-05-19 12:17:09 -04:00
Chris Troutner b8c1156de7 Merge pull request #72 from Permissionless-Software-Foundation/ct-unstable
Adding debugging to usage tracking
2025-05-18 15:40:31 -07:00
Chris Troutner 5146155555 Merge remote-tracking branch 'upstream/master' into ct-unstable 2025-05-18 15:38:43 -07:00
Chris Troutner af83c06b43 Merge pull request #174 from Permissionless-Software-Foundation/ct-unstable
fix(usage): Debugging issue with usage tracking
2025-05-18 15:37:46 -07:00
Chris Troutner e6317c3282 fix(usage): Debugging issue with usage tracking 2025-05-18 15:36:32 -07:00
Chris Troutner 354321c956 Merge pull request #173 from Permissionless-Software-Foundation/ct-unstable
fix(wallet.adapter): Binding this object to all methods
2025-04-22 19:07:06 -07:00
Chris Troutner af2572a493 fix(wallet.adapter): Binding this object to all methods 2025-04-22 18:58:03 -07:00
Chris Troutner ac451fd601 Merge pull request #71 from Permissionless-Software-Foundation/ct-unstable
Passing pagination paramter through to PSFFPP
2025-04-16 07:38:00 -07:00
Chris Troutner 6857fe0166 linting 2025-04-16 07:29:40 -07:00
Chris Troutner 89a03e561a feat(GET /pins/:page): Adding pagenation to endpoint 2025-04-16 07:29:27 -07:00
Chris Troutner 22e32a6fc1 Merge pull request #70 from Permissionless-Software-Foundation/ct-unstable
fix(psffpp): Updating to psffpp v1.2.0
2025-04-12 21:05:52 -07:00
Chris Troutner fcf5db7fa4 fix(psffpp): Updating to psffpp v1.2.0 2025-04-12 21:04:17 -07:00
Chris Troutner f8cf903fb9 Merge pull request #69 from Permissionless-Software-Foundation/dh-pin-claim-endpoint
feat(pin): Added pin-claim endpoint to ipfs-bch-wallet-consumer
2025-03-29 10:57:16 -07:00
Daniel Gonzalez c7986d1986 feat(pin): Added pin-claim endpoint to ipfs-bch-wallet-consumer 2025-03-27 11:01:30 -04:00
Chris Troutner 87ca75425b Merge pull request #68 from Permissionless-Software-Foundation/ct-unstable
feat(GET /ipfs/download/:cid): Download CID as readable stream
2025-02-27 18:46:57 -07:00
Chris Troutner 587114e827 feat(GET /ipfs/download/:cid): Download CID as readable stream 2025-02-27 18:45:06 -07:00
Chris Troutner c3a694546d Fixing mintor code formatting issues 2025-02-25 13:24:10 -07:00
Chris Troutner 591bd618e4 Merge pull request #67 from Permissionless-Software-Foundation/ct-unstable
Helia V5
2025-02-23 10:46:30 -07:00
Chris Troutner 166f2de20e fix(prod): Switching back to ct-unstable branch 2025-02-23 10:44:45 -07:00
Chris Troutner 35fbc23fe0 Syncing with upstream ipfs-service-provider. Fixing merge conflicts 2025-02-23 10:44:10 -07:00
Chris Troutner c65d662630 Merge pull request #172 from Permissionless-Software-Foundation/ct-unstable
fix(config): Added webRTC port & connection preference reporting
2025-02-23 10:37:30 -07:00
Chris Troutner 1b99f8afa1 fix(config): Added webRTC port & connection preference reporting 2025-02-23 10:34:00 -07:00
Chris Troutner 1bba636e43 Syncing with upstream ipfs-service-provider. Fixing merge conflicts 2025-02-23 10:25:55 -07:00
Chris Troutner c6f0a1317c Merge pull request #171 from Permissionless-Software-Foundation/ct-unstable
fix(prod): Switching Docker container to master branch
2025-02-23 09:57:41 -07:00
Chris Troutner fad5f8e23d fix(prod): Switching Docker container to master branch 2025-02-23 09:56:13 -07:00
Chris Troutner ec6e0f5e46 Merge pull request #170 from Permissionless-Software-Foundation/helia-v5
Helia v5
2025-02-23 09:46:10 -07:00
Chris Troutner f1caef2c0d fix(prod): Adding connection prefrence to production startup script 2025-02-23 09:41:55 -07:00
Chris Troutner 55bf9424f9 fix(libp2p): Updating deps to prevent AbortError issue 2702 2025-02-23 09:01:17 -07:00
Chris Troutner aa100c97ed fix(helia-coord): Updating to v1.7.1 2025-02-20 22:41:25 -07:00
Chris Troutner 9fa5eafe95 removing relay advetising deprecated setting 2025-02-20 20:51:40 -07:00
Chris Troutner 4d9e948e30 fix(helia): Manually porting @achingbrian PR to v5 branch 2025-02-20 20:51:09 -07:00
Chris Troutner e5cdf28e34 Catching and reporting AbortError race condition 2025-02-20 19:03:45 -07:00
Chris Troutner 1d82b36105 fix(helia): Updated to 1.7.0 with webRTC and circuit relay enabled 2025-02-16 15:27:43 -07:00
Chris Troutner b910cd1e6d fix(webRTC): Adding /p2p-circuit multiaddr + ping service 2025-02-16 09:29:18 -07:00
Chris Troutner 536861ba99 Re-enabled webRTC and circuit relays 2025-02-14 12:53:12 -07:00
Chris Troutner ef030e3dc6 fix(libp2p): Updated to v2.6.2 with node.js webrtc fix 2025-02-14 09:52:09 -07:00
Chris Troutner 3da46c3d6f Using helia-v5 branch when building docker container 2025-02-12 16:22:44 -07:00
Chris Troutner 82f5552836 feat(helia v5): Generating and saving new private key 2025-02-12 14:58:11 -07:00
Chris Troutner 4d25681c51 fix(helia v5): Changes to get node to connect to other nodes with helia v5 2025-02-12 09:22:36 -07:00
Chris Troutner 8fe78037c1 Merge pull request #66 from Permissionless-Software-Foundation/ct-unstable
GET /ipfs/cid2json
2025-02-09 06:53:22 -07:00
Chris Troutner 33a447cd61 fix(cid2json): Added error handler for corner case 2025-02-09 06:39:07 -07:00
Chris Troutner 8a70d6a169 fix(cid2json): Works with CID files and directories 2025-02-09 06:12:47 -07:00
Chris Troutner 9ae5e890ae feat(cid2json): Got basic functionality in place, no tests yet, known issues 2025-02-09 05:52:48 -07:00
Chris Troutner 1bf28cbc21 Merge pull request #65 from Permissionless-Software-Foundation/ct-unstable
Removing logging of usage middleware
2025-01-27 19:00:06 -07:00
Chris Troutner d7bb6e4cb2 Merge remote-tracking branch 'upstream/master' into ct-unstable 2025-01-27 18:58:48 -07:00
Chris Troutner 98a1631498 Merge pull request #167 from Permissionless-Software-Foundation/ct-unstable
Removing logging of usage middleware
2025-01-27 18:55:29 -07:00
Chris Troutner 8dce8cbf8d Removing logging of usage middleware 2025-01-27 18:53:17 -07:00
Chris Troutner f097ee024a Merge pull request #64 from Permissionless-Software-Foundation/ct-unstable
Syncing with upstream ipfs-service-provider
2025-01-26 19:53:20 -07:00
Chris Troutner 7d21fe02f8 Syncing with upstream ipfs-service-provider 2025-01-26 19:48:38 -07:00
Chris Troutner 0b3049138b Merge pull request #166 from Permissionless-Software-Foundation/dh-usage-tests
feat(tests): Added Usage Unit Tests
2025-01-17 05:32:27 -07:00
Daniel Gonzalez d7136583b8 feat(tests): Added Usage Unit Tests 2025-01-15 17:59:09 -04:00
Chris Troutner 173f706a61 Merge pull request #63 from Permissionless-Software-Foundation/ct-unstable
Syncing with upstream ipfs-service-provider
2025-01-12 17:14:50 -07:00
Chris Troutner 39fc919d35 fix(pedigree): Updating pedigree doc 2025-01-12 17:12:15 -07:00
Chris Troutner 6f0726aeb8 Synced with upstream ipfs-service-provider 2025-01-12 17:11:09 -07:00
Chris Troutner 2568818ea1 Merge pull request #62 from Permissionless-Software-Foundation/ct-unstable
fix(CORS): Adding OPTIONS as an allowed method
2025-01-12 17:03:02 -07:00
Chris Troutner c721da1562 Merge pull request #165 from Permissionless-Software-Foundation/usage
Usage
2024-12-07 21:28:57 -08:00
Chris Troutner 3606a2d31b code cleanup 2024-12-07 21:27:41 -08:00
Chris Troutner 764905b041 fix(usage): Adding endpoints to analyize 24 hour usage of the REST API 2024-12-07 21:25:10 -08:00
Chris Troutner 556c6ec522 fix(usage): Adding Usage REST API 2024-12-07 20:48:08 -08:00
Chris Troutner 04395b70a6 Syncing with ct-unstable. Fixing merge conflict 2024-12-07 18:08:25 -08:00
Chris Troutner 4564090830 fix(CORS): Adding OPTIONS as an allowed method 2024-10-01 08:17:17 -07:00
Chris Troutner 81521151f4 Merge pull request #164 from Permissionless-Software-Foundation/ct-unstable
fix(ipfs): Disabling webRTC again
2024-09-18 12:02:22 -07:00
Chris Troutner 5a819c1d9c linting 2024-09-18 12:01:10 -07:00
Chris Troutner f2f3208fda fix(ipfs): Disabling webRTC again 2024-09-18 12:00:56 -07:00
Chris Troutner 7fe888cdda Merge pull request #163 from Permissionless-Software-Foundation/ct-unstable
fix(webRTC): Renabling webRTC & circuit relays
2024-09-17 07:50:59 -07:00
Chris Troutner db3f1bf2cf fix(webRTC): Renabling webRTC & circuit relays 2024-09-17 07:49:43 -07:00
Chris Troutner 512a49200d Merge pull request #61 from Permissionless-Software-Foundation/ct-unstable
Syncing with upstream ipfs-service-provider
2024-09-13 10:12:32 -07:00
Chris Troutner c6019df364 Merge remote-tracking branch 'upstream/master' into ct-unstable 2024-09-13 10:11:21 -07:00
Chris Troutner b7988253df Adding system-user-dev.json to gitignore 2024-09-13 10:11:08 -07:00
Chris Troutner 5f64257dca Merge pull request #162 from Permissionless-Software-Foundation/ct-unstable
fix(webrtc): Disabling webRTC due to bug
2024-09-13 10:07:53 -07:00
Chris Troutner 2fbdccb883 fix(webrtc): Disabling webRTC due to bug 2024-09-13 10:06:19 -07:00
Chris Troutner ecc10afac3 linting 2024-09-13 10:05:05 -07:00
Chris Troutner b55afd5f93 Merge pull request #60 from Permissionless-Software-Foundation/ct-unstable
Syncing with upstream ipfs-service-provider
2024-09-12 17:32:46 -07:00
Chris Troutner 12037a5e57 Syncing with upstream ipfs-service-provider 2024-09-12 17:31:28 -07:00
Chris Troutner 4fc54aa9c5 Merge pull request #161 from Permissionless-Software-Foundation/ct-unstable
fix(helia-coord): Updating to v1.5.14
2024-09-12 16:59:23 -07:00
Chris Troutner 2bd7f0c45c Merge branch 'master' into ct-unstable 2024-09-12 16:57:00 -07:00
Chris Troutner 900423339e fix(helia-coord): Updating to v1.5.14 2024-09-12 16:56:44 -07:00
Chris Troutner d907afa6bb Merge pull request #160 from Permissionless-Software-Foundation/ct-unstable
fix(helia-coord): Updating to v1.5.13
2024-09-12 16:48:03 -07:00
Chris Troutner 9b116d82b7 fix(helia-coord): Updating to v1.5.13 2024-09-12 16:42:43 -07:00
Chris Troutner 1d1bf50ee3 Merge pull request #159 from Permissionless-Software-Foundation/ct-unstable
Updating dependencies
2024-09-09 18:15:03 -07:00
Chris Troutner 5002004697 fix(minimal-slp-wallet): Upgraded to v5.12.0 2024-09-09 18:13:36 -07:00
Chris Troutner 5b0fac4301 Merge pull request #59 from Permissionless-Software-Foundation/ct-unstable
Updating dependencies
2024-09-05 10:10:29 -07:00
Chris Troutner 9584810b16 fix(deps): Updating dependencies 2024-09-05 10:06:11 -07:00
Chris Troutner f311a3b9c6 fix(webRTC): Upgrading to new package, adding required config 2024-08-29 11:00:07 -07:00
Chris Troutner 83f16709d8 Merge remote-tracking branch 'upstream/master' into ct-unstable 2024-08-24 08:17:31 -07:00
Chris Troutner fe91f4b875 Merge pull request #158 from Permissionless-Software-Foundation/ct-unstable
fix(helia-coord): Updating to v1.5.12
2024-08-23 10:30:27 -07:00
Chris Troutner 2d9a2b811c fix(helia-coord): Updating to v1.5.12 2024-08-23 10:24:21 -07:00
Chris Troutner 9360cf6f77 Syncing with upstream, updating minimal-slp-wallet to v5.11.3 2024-08-23 07:24:10 -07:00
Chris Troutner 514b65ed24 Merge pull request #157 from Permissionless-Software-Foundation/ct-unstable
fix(minimal-slp-wallet): Updated to v5.11.3
2024-08-23 07:02:19 -07:00
Chris Troutner 0c7f3088a8 fix(minimal-slp-wallet): Updated to v5.11.3 2024-08-23 06:59:58 -07:00
Chris Troutner aef88eb369 Merge pull request #58 from Permissionless-Software-Foundation/ct-unstable
Upgrading helia to v4.2.5
2024-08-20 08:17:49 -07:00
Chris Troutner 2f6cc72f41 Adding system user .json files 2024-08-20 08:16:37 -07:00
Chris Troutner 12cab02300 Merge pull request #156 from Permissionless-Software-Foundation/ct-unstable
Upgrading helia to v4.2.5
2024-08-20 07:53:53 -07:00
Chris Troutner 04a908261a Updating requirements in README 2024-08-20 07:32:30 -07:00
Chris Troutner f16eacc3f2 feat(helia): Updated helia and libp2p by syncing with upstream ipfs-service-provider 2024-08-19 13:22:08 -07:00
Chris Troutner 83898742f1 fix(webRTC): Falling back from v4.1.7 to v4.0.34 2024-08-19 07:35:59 -07:00
Chris Troutner 1795918719 Using node.js v20 for docker container 2024-08-19 06:33:31 -07:00
Chris Troutner 6894217078 fix(production): Limiting docker to 1GB of memory 2024-08-19 06:29:16 -07:00
Chris Troutner 3ffbed27b5 fix(helia-coord): Got running with v1.5.11 2024-08-18 08:41:28 -07:00
Chris Troutner c30b697bc6 linting 2024-07-26 11:29:10 -07:00
Chris Troutner df2f69050b feat(/price/psffpp): New REST endpoint for retrieving PSFFPP write price 2024-07-26 11:29:00 -07:00
Chris Troutner b49979971c lib added, but write price error defaults to hard-coded value 2024-07-25 07:35:49 -07:00
Chris Troutner 7a620892d7 Merge pull request #57 from Permissionless-Software-Foundation/ct-unstable
Updating helia-coord
2024-07-21 15:54:31 -07:00
Chris Troutner b372e3d260 Merge pull request #155 from Permissionless-Software-Foundation/ct-unstable
fix(Mongo): Skipping some tests when noMongo is true
2024-06-09 09:12:31 -07:00
Chris Troutner 707b385b8a linting 2024-06-09 09:11:09 -07:00
Chris Troutner 6cb140a491 fix(Mongo): Skipping some tests when noMongo is true 2024-06-09 09:10:55 -07:00
Chris Troutner c43ee8c25a Merge pull request #154 from Permissionless-Software-Foundation/ct-unstable
feat(Mongo): Config variable can disable MongoDB usage and dependency in Docker
2024-06-09 08:38:00 -07:00
Chris Troutner 0a7ec11510 feat(Mongo): Config variable can disable MongoDB usage and dependency in Docker 2024-06-09 08:28:40 -07:00
Chris Troutner 4a1524629c Merge remote-tracking branch 'upstream/master' into ct-unstable 2024-05-21 20:33:37 -07:00
Chris Troutner f7aaba71d1 Merge pull request #153 from Permissionless-Software-Foundation/ct-unstable
fix(helia-coord): Updating to v1.5.8
2024-05-04 09:38:28 -07:00
Chris Troutner 418a2e2ad2 fix(helia-coord): Updating to v1.5.8 2024-05-04 09:37:10 -07:00
Chris Troutner 74248ea0c7 Merge pull request #56 from Permissionless-Software-Foundation/ct-unstable
Improvements from production testing
2024-04-28 20:40:25 -07:00
Chris Troutner 70a69e98bb fix(pollForBchServices()): filtering for only wallet-services, not consumers 2024-04-28 16:53:34 -07:00
Chris Troutner 8cffd7fae3 Merge remote-tracking branch 'upstream/master' into ct-unstable 2024-04-27 13:22:07 -07:00
Chris Troutner a1023e8224 Merge pull request #152 from Permissionless-Software-Foundation/ct-unstable
fix(Docker): Setting memory limit at 4GB
2024-04-27 13:20:20 -07:00
Chris Troutner a32ed63d47 fix(Docker): Setting memory limit at 4GB 2024-04-27 13:17:57 -07:00
Chris Troutner 934fb46ffc Merge pull request #151 from Permissionless-Software-Foundation/ct-unstable
fix(Docker): Removing Volume statement
2024-04-26 14:40:41 -07:00
Chris Troutner b27136d03b fix(Docker): Removing Volume statement 2024-04-26 14:39:18 -07:00
Chris Troutner 064ac36dc7 linting 2024-04-26 14:38:17 -07:00
Chris Troutner 5e2cf86f4b Checking in code before switching branches 2024-04-26 14:38:03 -07:00
Chris Troutner 425d2ada65 Binding 'this' object to some subfunctions 2024-04-20 07:14:17 -07:00
Chris Troutner 9837d6a7fb Merge remote-tracking branch 'upstream/master' into ct-unstable 2024-04-13 07:15:57 -07:00
Chris Troutner 60a3d67c0b Merge pull request #150 from Permissionless-Software-Foundation/ct-unstable
fix(config): web2Api can be set with env var
2024-04-13 07:11:20 -07:00
Chris Troutner a03c5d8c91 fix(config): web2Api can be set with env var 2024-04-13 07:09:45 -07:00
Chris Troutner 0e7afb5f51 Merge remote-tracking branch 'upstream/master' into ct-unstable 2024-04-13 07:07:44 -07:00
Chris Troutner bcc90da49b Merge pull request #149 from Permissionless-Software-Foundation/ct-unstable
fix(config): Adding web2Api property to jsonLd config
2024-04-13 07:04:02 -07:00
Chris Troutner b473a3803c fix(config): Adding web2Api property to jsonLd config 2024-04-13 07:02:32 -07:00
Chris Troutner 38ed9a8dac Merge pull request #55 from Permissionless-Software-Foundation/ct-unstable
fix(getPins): Adding getPins endpoint for File Pin Service
2024-04-12 10:46:01 -07:00
Chris Troutner 0ca46df43e fix(getPins): Adding getPins endpoint for File Pin Service 2024-04-12 10:44:03 -07:00
Chris Troutner b5f09fbab9 Sycing with upstream ipfs-service-provider 2024-04-12 08:35:49 -07:00
Chris Troutner e3e928b33d Adding getPins endpoint for File Pin Service 2024-04-12 08:34:39 -07:00
Chris Troutner 9614f186b4 Merge pull request #148 from Permissionless-Software-Foundation/ct-unstable
fix(helia-coord): Updating to v1.5.7
2024-04-12 08:32:13 -07:00
Chris Troutner 0390f2d956 fix(helia-coord): Updating to v1.5.7 2024-04-12 08:30:14 -07:00
Chris Troutner 43025f0265 Merge pull request #54 from Permissionless-Software-Foundation/ct-unstable
Adding endpoints needed for metrics
2024-04-11 05:36:56 -07:00
Chris Troutner 7ea17fb57c Removing example timer controller 2024-04-11 05:34:25 -07:00
Chris Troutner efb29104d7 feat(/ipfs/file-info/:cid): GET endpoint to get CID pin metadata from ipfs-file-pin-server over JSON RPC 2024-04-10 19:41:43 -07:00
Chris Troutner 971474d493 linting 2024-04-10 11:05:55 -07:00
Chris Troutner 71a6de03b3 feat(/bch/service): GET endpoint to get ipfs-bch-wallet-service consumed by this app 2024-04-10 11:05:46 -07:00
Chris Troutner 98dd904fa5 feat(/ipfs/service): GET endpoint to retrieve File Pin service used by this app 2024-04-10 10:52:07 -07:00
Chris Troutner b5c7e2efb3 Using filename from file metadata 2024-04-09 22:00:58 -07:00
Chris Troutner 3c9bbb097e Successfully recieving result from file pin JSON RPC call 2024-04-09 20:56:40 -07:00
Chris Troutner 8956968027 Commenting out downloadCid2() 2024-04-09 08:08:53 -07:00
Chris Troutner 14c724bf9c Prototyping IPFS file download 2024-04-09 06:36:23 -07:00
Chris Troutner a9f7e609e9 Merge pull request #53 from Permissionless-Software-Foundation/ct-unstable
fix(docker): Changing ports to comply with cashstack.info
2024-04-07 14:41:49 -07:00
Chris Troutner fee68e0f59 fix(docker): Changing ports to comply with cashstack.info 2024-04-07 14:38:40 -07:00
Chris Troutner 86c005c205 Merge pull request #52 from Permissionless-Software-Foundation/ct-unstable
fix(jsonLd): Updating jsonLd details passed to other nodes
2024-04-03 09:36:10 -07:00
Chris Troutner d4d5b2eb68 fix(jsonLd): Updating jsonLd details passed to other nodes 2024-04-03 09:34:43 -07:00
86 changed files with 14307 additions and 10214 deletions
+2 -1
View File
@@ -62,7 +62,7 @@ docs
.nyc_output
coverage
database/
system-user-*.json
#system-user-*.json
orbitdb
ipfsdata
.ipfsdata
@@ -73,3 +73,4 @@ run-dev.sh
!README.md
wallet.json
system-user-dev.json
+1 -1
View File
@@ -1,5 +1,5 @@
The MIT License (MIT)
Copyright (c) 2021-2023 Permissionless Software Foundation
Copyright (c) 2021-2024 Permissionless Software Foundation
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+1 -1
View File
@@ -1,3 +1,3 @@
# Pedigree
This repository was forked from [koa-api-boilerplate](https://github.com/christroutner/koa-api-boilerplate). Code changes from that upstream repository is frequently pulled in and merged to this repository.
This repository was forked from [ipfs-service-provider](https://github.com/Permissionless-Software-Foundation/ipfs-service-provider). Code changes from that upstream repository is frequently pulled in and merged to this repository.
+3 -3
View File
@@ -12,9 +12,9 @@ More details are available at [CashStack.info](https://CashStack.info).
## Requirements
- node **^16.20.2**
- npm **^8.19.4**
- Docker **^20.10.8**
- node **^20.16.0**
- npm **^10.8.1**
- Docker **^24.0.7**
- Docker Compose **^1.27.4**
## Installation
+26 -15
View File
@@ -25,6 +25,7 @@ import config from '../config/index.js' // this first.
import AdminLib from '../src/adapters/admin.js'
import errorMiddleware from '../src/controllers/rest-api/middleware/error.js'
import { usageMiddleware } from '../src/use-cases/usage-use-cases.js'
import wlogger from '../src/adapters/wlogger.js'
import Controllers from '../src/controllers/index.js'
import { applyPassportMods } from '../config/passport.js'
@@ -45,16 +46,18 @@ class Server {
const app = new Koa()
app.keys = [this.config.session]
// Connect to the Mongo Database.
this.mongoose.Promise = global.Promise
this.mongoose.set('useCreateIndex', true) // Stop deprecation warning.
console.log(
`Connecting to MongoDB with this connection string: ${this.config.database}`
)
await this.mongoose.connect(this.config.database, {
useUnifiedTopology: true,
useNewUrlParser: true
})
if (!this.config.noMongo) {
// Connect to the Mongo Database.
this.mongoose.Promise = global.Promise
this.mongoose.set('useCreateIndex', true) // Stop deprecation warning.
console.log(
`Connecting to MongoDB with this connection string: ${this.config.database}`
)
await this.mongoose.connect(this.config.database, {
useUnifiedTopology: true,
useNewUrlParser: true
})
}
console.log(`Starting environment: ${this.config.env}`)
console.log(`Debug level: ${this.config.debugLevel}`)
@@ -65,6 +68,7 @@ class Server {
app.use(bodyParser())
app.use(session())
app.use(errorMiddleware())
app.use(usageMiddleware())
// Used to generate the docs.
app.use(mount('/', serve(`${process.cwd()}/docs`)))
@@ -81,7 +85,10 @@ class Server {
// Enable CORS for testing
// THIS IS A SECURITY RISK. COMMENT OUT FOR PRODUCTION
// Dev Note: This line must come BEFORE controllers.attachRESTControllers()
app.use(cors({ origin: '*' }))
app.use(cors({
origin: '*',
allowMethods: 'GET,HEAD,PUT,POST,DELETE,PATCH,OPTIONS'
}))
// Wait for any adapters to initialize.
await this.controllers.initAdapters()
@@ -102,9 +109,11 @@ class Server {
this.server = await app.listen(this.config.port)
console.log(`Server started on ${this.config.port}`)
// Create the system admin user.
const success = await this.adminLib.createSystemUser()
if (success) console.log('System admin user created.')
if (!this.config.noMongo) {
// Create the system admin user.
const success = await this.adminLib.createSystemUser()
if (success) console.log('System admin user created.')
}
// Attach the other IPFS controllers.
// Skip if this is a test environment.
@@ -116,7 +125,9 @@ class Server {
console.log('\nConfiguration:')
console.log(`Circuit Relay: ${this.config.isCircuitRelay}`)
console.log(`IPFS TCP port: ${this.config.ipfsTcpPort}`)
console.log(`IPFS WS port: ${this.config.ipfsWsPort}\n`)
console.log(`IPFS WS port: ${this.config.ipfsWsPort}`)
console.log(`IPFS WebRTC port: ${this.config.ipfsWebRtcPort}`)
console.log(`Connection preference: ${this.config.connectPref}\n`)
return app
} catch (err) {
+24 -6
View File
@@ -27,7 +27,7 @@ const ipfsCoordName = process.env.COORD_NAME
export default {
// Configure TCP port.
port: process.env.PORT || 5005,
port: process.env.PORT || 5015,
// Password for HTML UI that displays logs.
logPass: 'test',
@@ -43,6 +43,9 @@ export default {
? process.env.EMAILPASS
: 'emailpassword',
// Enable or Disable the usage of Mongo DB.
noMongo: process.env.NO_MONGO ? true : false,
// BEGIN WALLET CONFIGURATION
// BCH Mnemonic for generating encryption keys and payment address
@@ -111,20 +114,25 @@ export default {
'@type': 'WebAPI',
name: ipfsCoordName,
version,
protocol: 'generic-service',
protocol: 'ipfs-bch-wallet-consumer',
description:
'This is a generic IPFS Serivice Provider that uses JSON RPC over IPFS to communicate with it. This instance has not been customized. Source code: https://github.com/Permissionless-Software-Foundation/ipfs-service-provider',
documentation: 'https://ipfs-service-provider.fullstack.cash/',
'REST API gateway for accessing BCH blockchain services over the web3 Cash Stack (https://CashStack.info)',
documentation: 'https://ipfs-bch-wallet-consumer.fullstack.cash/',
provider: {
'@type': 'Organization',
name: 'Permissionless Software Foundation',
url: 'https://PSFoundation.cash'
}
},
// If this node has an IP4 address or domain name used to provide a REST API.
web2Api: process.env.WEB2_API ? process.env.WEB2_API : null
},
// IPFS Ports
ipfsTcpPort: process.env.IPFS_TCP_PORT ? process.env.IPFS_TCP_PORT : 4101,
ipfsWsPort: process.env.IPFS_WS_PORT ? process.env.IPFS_WS_PORT : 4103,
ipfsWebRtcPort: process.env.IPFS_WEB_RTC_PORT ? process.env.IPFS_WEB_RTC_PORT : 4105,
connectPref: process.env.CONNECT_PREF, // Used in helia-coord to select connection preference.
// Settings for production, using external go-ipfs node.
isProduction: process.env.SVC_ENV === 'prod' ? true : false,
@@ -159,6 +167,16 @@ export default {
// Preferred P2WDB provider
preferredP2wdbProvider: process.env.PREFERRED_P2WDB_PROVIDER
? process.env.PREFERRED_P2WDB_PROVIDER
: ''
: '',
// Preferred P2WDB provider
preferredIpfsFileProvider: process.env.PREFERRED_IPFS_FILE_PROVIDER
? process.env.PREFERRED_IPFS_FILE_PROVIDER
: '',
// Account Configuration
disableNewAccounts: process.env.DISABLE_NEW_ACCOUNTS ? true : false,
// Admin password
adminPassword: process.env.ADMIN_PASSWORD
}
+1 -1
View File
@@ -4,7 +4,7 @@ import development from './env/development.js'
import production from './env/production.js'
import test from './env/test.js'
const env = process.env.SVC_ENV || 'development'
const env = process.env.CONSUMER_ENV || 'development'
console.log(`Loading config for this environment: ${env}`)
let config = development
+8
View File
@@ -0,0 +1,8 @@
{
"email": "system@system.com",
"name": "admin",
"password": "c9JjJxZmTs5bWmWC6adQ",
"type": "admin",
"id": "68d47521e6060ba7332301b1",
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjY4ZDQ3NTIxZTYwNjBiYTczMzIzMDFiMSIsImlhdCI6MTc2MDY0MzI5NH0.SgoWnrP0YZZtPZ2V_C7HsfKoRHphfoIODN3-pltsGbo"
}
+6
View File
@@ -0,0 +1,6 @@
{
"password": "02PjvhoMGoM7pJylp80Z",
"email": "system@system.com",
"id": "645e49a45a9f5a8e0a54a083",
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjY0NWU0OWE0NWE5ZjVhOGUwYTU0YTA4MyIsImlhdCI6MTY4MzkwMDgzNn0.sXeT-G9kS5Nu1dnZUGsNm6FhxrcNaCHhnQ-tLqsJc9E"
}
+8
View File
@@ -0,0 +1,8 @@
{
"email": "system@system.com",
"name": "admin",
"password": "NR0S1fPGLOAGUwPfKpJe",
"type": "admin",
"id": "698277538440afae85603e83",
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjY5ODI3NzUzODQ0MGFmYWU4NTYwM2U4MyIsImlhdCI6MTc3MDE1NzkwN30.rBILkWIXFtj0OkGBQyJyf89i9S2SYhm4ETPhvevVwaQ"
}
+4
View File
@@ -1,4 +1,8 @@
import Server from './bin/server.js'
const server = new Server()
process.on('unhandledRejection', (reason, promise) => {
console.log(`Handling ${reason.code} error. stack: `, reason)
})
server.startServer()
+8075 -8763
View File
File diff suppressed because it is too large Load Diff
+18 -21
View File
@@ -1,16 +1,17 @@
{
"name": "ipfs-bch-wallet-consumer",
"version": "3.0.0",
"version": "3.1.1",
"description": "A (local) REST API that proxies BCH wallet services delivered over IPFS (global).",
"main": "index.js",
"type": "module",
"scripts": {
"start": "node index.js",
"test": "npm run test:all",
"test": "npm run test:unit",
"test:all": "export CONSUMER_ENV=test && c8 --reporter=text mocha --exit --timeout 15000 --recursive test/unit test/e2e/automated/",
"test:unit": "export CONSUMER_ENV=test && mocha --exit --timeout 15000 --recursive test/unit/",
"test:unit": "export CONSUMER_ENV=test && c8 --reporter=text mocha --exit --timeout 15000 --recursive test/unit/",
"test:e2e:auto": "export CONSUMER_ENV=test && mocha --exit --timeout 15000 test/e2e/automated/",
"test:temp": "export CONSUMER_ENV=test && mocha --exit --timeout 15000 -g '#rate-limit' test/unit/json-rpc/",
"start:e2e:server": "export CONSUMER_ENV=test && node index.js",
"test:temp": "export SVC_ENV=test && mocha --exit --timeout 15000 -g '#rate-limit' test/unit/json-rpc/",
"lint": "standard --env mocha --fix",
"docs": "./node_modules/.bin/apidoc -i src/ -o docs",
"coverage": "c8 report --reporter=text-lcov | coveralls",
@@ -24,24 +25,10 @@
},
"repository": "Permissionless-Software-Foundation/ipfs-bch-wallet-consumer",
"dependencies": {
"@chainsafe/libp2p-gossipsub": "11.0.1",
"@chainsafe/libp2p-noise": "14.1.0",
"@chainsafe/libp2p-yamux": "6.0.1",
"@helia/unixfs": "3.0.1",
"@libp2p/bootstrap": "10.0.7",
"@libp2p/circuit-relay-v2": "1.0.13",
"@libp2p/identify": "1.0.6",
"@libp2p/keychain": "4.0.7",
"@libp2p/logger": "4.0.5",
"@libp2p/tcp": "9.0.7",
"@libp2p/webrtc": "4.0.17",
"axios": "0.27.2",
"bcryptjs": "2.4.3",
"blockstore-fs": "1.1.8",
"datastore-fs": "9.1.6",
"glob": "7.1.6",
"helia": "2.1.0",
"helia-coord": "1.5.6",
"helia-coord": "2.0.2",
"jsonrpc-lite": "2.2.0",
"jsonwebtoken": "8.5.1",
"jwt-bch-lib": "1.3.0",
@@ -56,13 +43,14 @@
"koa-router": "10.0.0",
"koa-static": "5.0.0",
"koa2-ratelimit": "0.9.1",
"libp2p": "1.2.1",
"line-reader": "0.4.0",
"minimal-slp-wallet": "5.11.2",
"mime-types": "2.1.35",
"minimal-slp-wallet": "7.1.4",
"mongoose": "5.13.14",
"node-fetch": "npm:@achingbrain/node-fetch@2.6.7",
"nodemailer": "6.7.5",
"passport-local": "1.0.0",
"psffpp": "1.2.0",
"public-ip": "6.0.1",
"semver": "7.3.5",
"winston": "3.3.3",
@@ -98,5 +86,14 @@
"ignore": [
"/test/unit/mocks/**/*.js"
]
},
"overrides": {
"@multiformats/multiaddr-to-uri": "10.1.2",
"@multiformats/multiaddr-matcher": "1.6.0",
"@libp2p/utils": "6.7.1",
"helia": {
"libp2p": "2.7.2",
"@libp2p/upnp-nat": "3.0.0"
}
}
}
+2 -7
View File
@@ -4,7 +4,7 @@
#IMAGE BUILD COMMANDS
# ct-base-ubuntu = ubuntu 18.04 + nodejs v10 LTS
#FROM christroutner/ct-base-ubuntu
FROM ubuntu:20.04
FROM ubuntu:24.04
MAINTAINER Chris Troutner <chris.troutner@gmail.com>
#Update the OS and install any OS packages needed.
@@ -15,7 +15,7 @@ RUN apt-get install -y sudo git curl nano gnupg wget
RUN sudo apt-get install -y ca-certificates curl gnupg
RUN sudo mkdir -p /etc/apt/keyrings
RUN curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | sudo gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg
RUN echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_16.x nodistro main" | sudo tee /etc/apt/sources.list.d/nodesource.list
RUN echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_22.x nodistro main" | sudo tee /etc/apt/sources.list.d/nodesource.list
RUN sudo apt-get update
RUN sudo apt-get install -y nodejs build-essential
@@ -66,11 +66,6 @@ RUN npm install
# Generate the API docs
RUN npm run docs
VOLUME /home/safeuser/keys
# Expose the port the API will be served on.
EXPOSE 5010
# Start the application.
#COPY start-production.sh start-production.sh
CMD ["./start-production.sh"]
+8 -5
View File
@@ -7,7 +7,7 @@ services:
image: mongo:4.2.0
container_name: mongo-bch-consumer
ports:
- '5555:27017' # <host port>:<container port>
- '5558:27017' # <host port>:<container port>
volumes:
- ../data/database:/data/db
command: mongod --logpath=/dev/null # -- quiet
@@ -22,13 +22,16 @@ services:
options:
max-size: '10m'
max-file: '10'
#mem_limit: 500mb
mem_limit: 1000mb
links:
- mongo-bch-consumer
ports:
- '5010:5010' # <host port>:<container port>
- '4001:4001'
- '4003:4003'
# <host port>:<container port>
- '5015:5015' # REST API
- '4101:4101' # TCP
- '4103:4103' # Websockets
- '4105:4105' # WebRTC
volumes:
- ../data/ipfsdata:/home/safeuser/ipfs-bch-wallet-consumer/.ipfsdata
- ./start-production.sh:/home/safeuser/ipfs-bch-wallet-consumer/start-production.sh
+11 -5
View File
@@ -20,20 +20,26 @@ export COORD_NAME=bch-consumer-generic
# Production database connection string.
export DBURL=mongodb://172.17.0.1:5555/ipfs-bch-consumer-prod
export DBURL=mongodb://172.17.0.1:5558/ipfs-bch-consumer-prod
# Configure REST API port
export PORT=5010
export PORT=5015
# Production settings using external go-ipfs node.
export SVC_ENV=prod
export CONSUMER_ENV=prod
export IPFS_HOST=172.17.0.1
export IPFS_API_PORT=5001
export IPFS_TCP_PORT=4001
export IPFS_WS_PORT=4003
export IPFS_TCP_PORT=4101
export IPFS_WS_PORT=4103
export IPFS_WEB_RTC_PORT=4105
# Set the debug level for helia-coord. 0-3.
# 0 = no debug logs. 3 = maximum debug logs.
export DEBUG_LEVEL=0
# Use this if the IPFS node has a publically accessible IP address.
#export CONNECT_PREF=direct
# Use this if the IPFS node is behind a NAT or Firewall and cannot be accessed directly.
export CONNECT_PREF=cr
npm start
+26 -58
View File
@@ -51,70 +51,37 @@ class Admin {
// used by the Listing Manager and test scripts, in order access private API
// functions.
async createSystemUser () {
// Create the system user.
try {
context.password = _this._randomString(20)
const options = {
method: 'POST',
url: `${LOCALHOST}/users`,
data: {
user: {
email: 'system@system.com',
password: context.password,
name: 'admin'
}
}
const context = {
email: 'system@system.com',
name: 'admin',
password: _this.config.adminPassword || _this._randomString(20),
type: 'admin'
}
const result = await _this.axios.request(options)
// console.log('admin.data: ', result.data)
// Check if the user already exists
let adminUser = await _this.User.findOne({ email: context.email })
context.email = result.data.user.email
context.id = result.data.user._id
context.token = result.data.token
if (adminUser) {
// Update the password
adminUser.password = context.password
} else {
// Create a new admin user
adminUser = new _this.User(context)
}
// Update context with the new user id and token
context.id = adminUser._id
context.token = await adminUser.generateToken()
// Get the mongoDB entry
const user = await _this.User.findById(context.id)
// Save the user
await adminUser.save()
// Change the user type to admin
user.type = 'admin'
// console.log(`user created: ${JSON.stringify(user, null, 2)}`)
// Save the user model.
await user.save()
// console.log(`admin user created: ${JSON.stringify(result.body, null, 2)}`)
// console.log(`with password: ${context.password}`)
// Write out the system user information to a JSON file that external
// applications like the Task Manager and the test scripts can access.
await jsonFiles.writeJSON(context, JSON_PATH)
// console.log('context: ', context)
// console.log('JSON_PATH: ', JSON_PATH)
// Write the user data to the JSON file
await _this.jsonFiles.writeJSON(context, JSON_PATH)
return context
} catch (err) {
// Handle existing system user.
if (err.response.status === 422) {
try {
// Delete the existing user
await _this.deleteExistingSystemUser()
// Call this function again.
return _this.createSystemUser()
} catch (err2) {
console.error(
'Error in admin.js/createSystemUser() while trying generate new system user.'
)
// process.end(1)
throw err2
}
} else {
console.log('Error in admin.js/createSystemUser: ')
// process.end(1)
throw err
}
} catch (error) {
console.log('Error in admin.js/createSystemUser()')
throw error
}
}
@@ -129,6 +96,7 @@ class Admin {
})
await _this.User.deleteOne({ email: 'system@system.com' })
return true
} catch (err) {
console.log('Error in admin.js/deleteExistingSystemUser()')
throw err
@@ -152,7 +120,7 @@ class Admin {
Accept: 'application/json'
},
data: {
email: 'system@system.com',
email: existingUser.email,
password: existingUser.password
}
}
+4 -1
View File
@@ -110,6 +110,9 @@ class BchAdapter {
// Choose the BCH wallet service to use.
async selectProvider (providerId) {
try {
if (!providerId || typeof providerId !== 'string') {
throw new Error('providerId must be a string!')
}
this.ipfs.ipfsCoordAdapter.config.preferredProvider = providerId
return true
@@ -405,7 +408,7 @@ class BchAdapter {
// Wait for data to come back from the wallet service.
const data = await this.waitForRPCResponse(rpcId)
console.log(`data: ${JSON.stringify(data, null, 2)}`)
// console.log(`data: ${JSON.stringify(data, null, 2)}`)
return data
} catch (err) {
+3 -1
View File
@@ -15,6 +15,7 @@ import Passport from './passport.js'
import Nodemailer from './nodemailer.js'
import BCH from './bch/index.js'
import P2WDB from './p2wdb/index.js'
import IpfsFiles from './ipfs-files/index.js'
// const { wlogger } = require('./wlogger')
import JSONFiles from './json-files.js'
@@ -40,11 +41,12 @@ class Adapters {
this.passport = new Passport()
this.nodemailer = new Nodemailer()
this.jsonFiles = new JSONFiles()
this.bchjs = new BCHJS()
this.bchjs = new BCHJS({ restURL: config.apiServer })
this.config = config
this.bch = new BCH(localConfig)
this.p2wdb = new P2WDB(localConfig)
this.wallet = new Wallet(localConfig)
this.ipfsFiles = new IpfsFiles(localConfig)
// Get a valid JWT API key and instance bch-js.
this.fullStackJwt = new FullStackJWT(config)
+336
View File
@@ -0,0 +1,336 @@
/*
This library contains code for interfacing to the ipfs-file-pin-service using
JSON RPC over IPFS. Most of these functions are called by the /files REST API
endpoints and the IPFS Use Cases library.
*/
// Public npm libraries
import { v4 as uid } from 'uuid'
import jsonrpc from 'jsonrpc-lite'
// Local libraries
import wlogger from '../wlogger.js'
// let _this
class IpfsFilesAdapter {
constructor (localConfig = {}) {
// console.log('BCH localConfig: ', localConfig)
this.ipfs = localConfig.ipfs
if (!this.ipfs) {
throw new Error(
'An instance of IPFS must be passed when instantiating the IPFS Files Adapter library.'
)
}
// this.eventEmitter = localConfig.eventEmitter
// if (!this.eventEmitter) {
// throw new Error(
// 'An instance of an EventEmitter must be passed when instantiating the adapters.'
// )
// }
// Connect the RPC handler when the event fires with new data.
// this.eventEmitter.on('rpcData', this.rpcHandler)
// Encapsulate dependencies
this.uid = uid
this.jsonrpc = jsonrpc
// A queue for holding RPC data that has arrived.
this.rpcDataQueue = []
// _this = this // Global handle on instance of this Class.
// Bind 'this' object to all subfunctions for this Class
this.rpcHandler = this.rpcHandler.bind(this)
this.getStatus = this.getStatus.bind(this)
this.selectProvider = this.selectProvider.bind(this)
this.getFileMetadata = this.getFileMetadata.bind(this)
this.getPins = this.getPins.bind(this)
this.waitForRPCResponse = this.waitForRPCResponse.bind(this)
this.pinClaim = this.pinClaim.bind(this)
}
// This handler is triggered when RPC data comes in over IPFS.
// Handle RPC input, and add the response to the RPC queue.
// Once in the queue, it will get processed by waitForRPCResponse()
rpcHandler (data) {
try {
// Convert string input into an object.
// const jsonData = JSON.parse(data)
// console.log(`JSON RPC response for ID ${data.payload.id} received.`)
this.rpcDataQueue.push(data)
} catch (err) {
console.error('Error in files/rpcHandler(): ', err)
// Do not throw error. This is a top-level function.
}
}
// Get the status of BCH wallet services this node can talk to. Returns an
// array of BCH wallet service providers.
async getStatus () {
try {
const peerData = this.ipfs.ipfsCoordAdapter.ipfsCoord.thisNode.peerData
// console.log(`peerData: ${JSON.stringify(peerData, null, 2)}`)
// const status = {
// state: this.ipfs.ipfsCoordAdapter.state
// }
// Add names to the IPFS IDs for each provider.
const initialServiceProviders =
this.ipfs.ipfsCoordAdapter.state.ipfsFileProviders
const serviceProviders = []
for (let i = 0; i < initialServiceProviders.length; i++) {
const thisProvider = initialServiceProviders[i]
const providerData = peerData.filter((x) => x.from === thisProvider)
if (providerData.length) {
const provObj = {
ipfsId: thisProvider,
name: providerData[0].data.jsonLd.name
}
serviceProviders.push(provObj)
}
}
// console.log(
// `serviceProviders: ${JSON.stringify(serviceProviders, null, 2)}`
// )
const outObj = {
serviceProviders,
selectedProvider: this.ipfs.ipfsCoordAdapter.state.selectedIpfsFileProvider
}
// console.log('outObj: ', outObj)
return outObj
} catch (err) {
// console.log('createUser() error: ', err)
wlogger.error('Error in adapters/files/getStatus()')
throw err
}
}
// Choose the ipfs-file-pin-service to use.
async selectProvider (providerId) {
try {
if (!providerId || typeof providerId !== 'string') {
throw new Error('providerId must be a string!')
}
this.ipfs.ipfsCoordAdapter.config.selectedIpfsFileProvider = providerId
return true
} catch (err) {
// console.log('createUser() error: ', err)
wlogger.error('Error in adapters/files/selectProvider()')
throw err
}
}
// Given a CID, query the ipfs-file-pin-service to get the file metadata, like
// the file name.
async getFileMetadata (inObj = {}) {
try {
const { cid } = inObj
// Input validation.
if (!cid || typeof cid !== 'string') {
throw new Error('getFileMetadata() cid input hash must be a string.')
}
// Throw an error if this IPFS node has not yet made a connection to a
// wallet service provider.
const selectedProvider =
this.ipfs.ipfsCoordAdapter.state.selectedIpfsFileProvider
if (!selectedProvider) {
throw new Error('No IPFS File Pin Service provider available yet.')
}
const rpcData = {
endpoint: 'getFileMetadata',
cid
}
// Generate a UUID for the call.
const rpcId = this.uid()
// Generate a JSON RPC command.
const cmd = this.jsonrpc.request(rpcId, 'file-pin', rpcData)
const cmdStr = JSON.stringify(cmd)
// console.log('cmdStr: ', cmdStr)
// Send the RPC command to selected wallet service.
const thisNode = this.ipfs.ipfsCoordAdapter.ipfsCoord.thisNode
await this.ipfs.ipfsCoordAdapter.ipfsCoord.useCases.peer.sendPrivateMessage(
selectedProvider,
cmdStr,
thisNode
)
// Wait for data to come back from the wallet service.
const data = await this.waitForRPCResponse(rpcId)
// console.log('getFileMetadata() data: ', data)
return data
} catch (err) {
wlogger.error('Error in adapters/files/getFileMetadata()')
throw err
}
}
// Get an array of the latest 20 pinned files.
async getPins (inObj = {}) {
try {
// Throw an error if this IPFS node has not yet made a connection to a
// wallet service provider.
const selectedProvider =
this.ipfs.ipfsCoordAdapter.state.selectedIpfsFileProvider
if (!selectedProvider) {
throw new Error('No IPFS File Pin Service provider available yet.')
}
const { page } = inObj
const rpcData = {
endpoint: 'getPins',
page
}
// Generate a UUID for the call.
const rpcId = this.uid()
// Generate a JSON RPC command.
const cmd = this.jsonrpc.request(rpcId, 'file-pin', rpcData)
const cmdStr = JSON.stringify(cmd)
// console.log('cmdStr: ', cmdStr)
// Send the RPC command to selected wallet service.
const thisNode = this.ipfs.ipfsCoordAdapter.ipfsCoord.thisNode
await this.ipfs.ipfsCoordAdapter.ipfsCoord.useCases.peer.sendPrivateMessage(
selectedProvider,
cmdStr,
thisNode
)
// Wait for data to come back from the wallet service.
const data = await this.waitForRPCResponse(rpcId)
// console.log('getFileMetadata() data: ', data)
return data
} catch (err) {
wlogger.error('Error in adapters/files/getPins()')
throw err
}
}
// Returns a promise that resolves to data when the RPC response is recieved.
async waitForRPCResponse (rpcId) {
try {
if (!rpcId) {
throw new Error('rpcId can not be false or undefined')
}
// Initialize variables for tracking the return data.
let dataFound = false
let cnt = 0
// Default return value, if the remote computer does not respond in time.
let data = {
success: false,
message: 'request timed out',
data: ''
}
// Loop that waits for a response from the service provider.
do {
// console.log(`this.rpcDataQueue.length: ${this.rpcDataQueue.length}`)
for (let i = 0; i < this.rpcDataQueue.length; i++) {
const rawData = this.rpcDataQueue[i]
// console.log(`rawData: ${JSON.stringify(rawData, null, 2)}`)
if (rawData.payload.id === rpcId) {
dataFound = true
// console.log('data was found in the queue')
data = rawData.payload.result.value
// Remove the data from the queue
this.rpcDataQueue.splice(i, 1)
break
}
}
// Wait between loops.
// await this.sleep(1000)
await this.ipfs.ipfsCoordAdapter.wallet.bchjs.Util.sleep(2000)
cnt++
// Exit if data was returned, or the window for a response expires.
} while (!dataFound && cnt < 10)
// console.log(`dataFound: ${dataFound}, cnt: ${cnt}`)
return data
} catch (err) {
console.error('Error in waitForRPCResponse()')
throw err
}
}
async pinClaim (inObj = {}) {
try {
// Throw an error if this IPFS node has not yet made a connection to a
// ipfs service provider.
const selectedProvider =
this.ipfs.ipfsCoordAdapter.state.selectedIpfsFileProvider
if (!selectedProvider) {
throw new Error('No IPFS File Provider Service is available yet. Try again in a few seconds.')
}
const { proofOfBurnTxid, cid, claimTxid, filename, address } = inObj
const rpcData = {
endpoint: 'pinClaim',
proofOfBurnTxid,
cid,
claimTxid,
filename,
address
}
// Generate a UUID for the call.
const rpcId = this.uid()
// Generate a JSON RPC command.
const cmd = this.jsonrpc.request(rpcId, 'file-pin', rpcData)
const cmdStr = JSON.stringify(cmd)
// console.log('cmdStr: ', cmdStr)
// Send the RPC command to selected wallet service.
const thisNode = this.ipfs.ipfsCoordAdapter.ipfsCoord.thisNode
await this.ipfs.ipfsCoordAdapter.ipfsCoord.useCases.peer.sendPrivateMessage(
selectedProvider,
cmdStr,
thisNode
)
// Wait for data to come back from the wallet service.
const data = await this.waitForRPCResponse(rpcId)
return data
} catch (err) {
console.error('Error in pinClaim()')
throw err
}
}
}
// module.exports = P2wdbAdapter
export default IpfsFilesAdapter
+114 -5
View File
@@ -20,9 +20,11 @@ import config from '../../../config/index.js'
// The minimum version of ipfs-bch-wallet-service that this wallet can work with.
const MIN_BCH_WALLET_VERSION = '1.11.11'
const WALLET_PROTOCOL = 'bch-wallet'
const WALLET_PROTOCOL = 'bch-wallet-service'
const MIN_P2WDB_VERSION = '1.4.0'
const P2WDB_PROTOCOL = 'p2wdb'
const MIN_FILE_PIN_VERSION = '1.0.0'
const FILE_PIN_PROTOCOL = 'ipfs-file-pin-service'
let _this
@@ -56,9 +58,13 @@ class IpfsCoordAdapter {
// Periodically poll services for available wallet service providers.
this.pollBchServiceInterval = setInterval(this.pollForBchServices, 10000)
this.pollIpfsFileServiceInterval = setInterval(
this.pollForIpfsFileServices,
11000
)
this.pollP2wdbServiceInterval = setInterval(
this.pollForP2wdbServices,
11000
12000
)
// State object. TODO: Make this more robust.
@@ -66,7 +72,9 @@ class IpfsCoordAdapter {
serviceProviders: [],
selectedServiceProvider: '',
p2wdbProviders: [],
selectedP2wdbProvider: ''
selectedP2wdbProvider: '',
ipfsFileProviders: [],
selectedIpfsFileProvider: ''
}
_this = this
@@ -211,9 +219,10 @@ class IpfsCoordAdapter {
}
}
} catch (err) {
console.log('Error in pollForP2wdbServices()', err)
// catch and handle known failure mode.
if (
err.message.includes("Cannot read property 'peerList' of undefined")
err.message.includes('Cannot read')
) {
return
}
@@ -268,6 +277,7 @@ class IpfsCoordAdapter {
// Persist the config setting, so it can be used by other commands.
// _this.conf.set('selectedService', thisPeer)
console.log(`---->BCH wallet service selected: ${thisPeer}`)
console.log('preferredProvider: ', _this.config.preferredProvider)
}
// console.log('preferredProvider: ', _this.config.preferredProvider)
@@ -278,6 +288,14 @@ class IpfsCoordAdapter {
_this.config.preferredProvider &&
thisPeer === _this.config.preferredProvider
) {
// console.log('preferredProvider: ', _this.config.preferredProvider)
// console.log('thisPeer: ', thisPeer)
// console.log('selectedServiceProvider: ', _this.state.selectedServiceProvider)
if (_this.state.selectedServiceProvider !== thisPeer) {
console.log(`---->BCH wallet service switched to preferred peer: ${thisPeer}`)
console.log('preferredProvider: ', _this.config.preferredProvider)
}
_this.state.selectedServiceProvider = thisPeer
}
@@ -298,7 +316,7 @@ class IpfsCoordAdapter {
} catch (err) {
// catch and handle known failure mode.
if (
err.message.includes("Cannot read property 'peerList' of undefined")
err.message.includes('Cannot read')
) {
return
}
@@ -308,6 +326,97 @@ class IpfsCoordAdapter {
}
}
// Poll the ipfs-coord coordination channel for available ipfs-file-pin-service
// providers. This method is called periodically by a timer-interval.
pollForIpfsFileServices () {
try {
// console.log('pollForIpfsFileServices() polling for BCH service')
// An array of IPFS IDs of other nodes in the coordination pubsub channel.
const peers = _this.ipfsCoord.thisNode.peerList
// console.log(`peers: ${JSON.stringify(peers, null, 2)}`)
// Array of objects. Each object is the IPFS ID of the peer and contains
// data about that peer.
const peerData = _this.ipfsCoord.thisNode.peerData
// console.log(`peerData: ${JSON.stringify(peerData, null, 2)}`)
for (let i = 0; i < peers.length; i++) {
const thisPeer = peers[i]
const thisData = peerData.filter((x) => x.from === thisPeer)
const thisPeerData = thisData[0]
// Create a 'fingerprint' that defines the wallet service.
const protocol = thisPeerData.data.jsonLd.protocol
const version = thisPeerData.data.jsonLd.version
// console.log(
// `debug: peer ${thisPeer} uses protocol: ${protocol} v${version}`,
// )
let versionMatches = false
if (version) {
// versionMatches = _this.semver.gt(version, MIN_FILE_PIN_VERSION)
versionMatches = _this.semver.satisfies(version, `>=${MIN_FILE_PIN_VERSION}`)
}
// Ignore any peers that don't match the fingerprint for a BCH wallet
// service.
if (protocol && protocol.includes(FILE_PIN_PROTOCOL) && versionMatches) {
// console.log('Matching peer: ', thisPeerData)
// Temporary business logic.
// Use the first available wallet service detected.
if (_this.state.ipfsFileProviders.length === 0) {
_this.state.selectedIpfsFileProvider = thisPeer
// Persist the config setting, so it can be used by other commands.
// _this.conf.set('selectedService', thisPeer)
console.log(`---->IPFS File service selected: ${thisPeer}`)
console.log('preferredIpfsFileProvider: ', _this.config.preferredIpfsFileProvider)
}
// console.log('preferredProvider: ', _this.config.preferredProvider)
// If a preferred provider is set in the config file, then connect
// to the preferred provider when it's discovered.
if (
_this.config.preferredIpfsFileProvider &&
thisPeer === _this.config.preferredIpfsFileProvider
) {
if (_this.state.selectedIpfsFileProvider !== thisPeer) {
console.log(`---->IPFS File service switched to preferred peer: ${thisPeer}`)
}
_this.state.selectedIpfsFileProvider = thisPeer
}
// console.log('selectedServiceProvider: ', _this.state.selectedServiceProvider)
// Check if the peer has already been added to the list of providers.
const alreadyExists = _this.state.ipfsFileProviders.filter(
(x) => x === thisPeer
)
// Add the peer to the list of serviceProviders if it doesn't already
// exist in the list.
if (!alreadyExists.length) {
_this.state.ipfsFileProviders.push(thisPeer)
}
}
}
} catch (err) {
// catch and handle known failure mode.
if (
err.message.includes('Cannot read')
) {
return
}
console.error('Error in pollForIpfsFileServices(): ', err)
// Do not throw error. This is a top-level function.
}
}
// This method handles input coming in from other IPFS peers.
// It passes the data on to the REST API library by emitting an event.
// peerInputHandler (data) {
+17 -205
View File
@@ -3,33 +3,12 @@
This library deals with IPFS so that the apps business logic doesn't need
to have any specific knowledge of the js-ipfs library.
TODO: Add the external IP address to the list of multiaddrs advertised by
this node. See this GitHub Issue for details:
https://github.com/Permissionless-Software-Foundation/ipfs-service-provider/issues/38
Node creation is delegated to helia-coord's CreateHeliaNode factory,
which encapsulates all helia/libp2p dependencies and configuration.
*/
// Global npm libraries
import { createHelia } from 'helia'
import fs from 'fs'
import { FsBlockstore } from 'blockstore-fs'
import { FsDatastore } from 'datastore-fs'
import { createLibp2p } from 'libp2p'
import { tcp } from '@libp2p/tcp'
import { noise } from '@chainsafe/libp2p-noise'
import { yamux } from '@chainsafe/libp2p-yamux'
// import { bootstrap } from '@libp2p/bootstrap'
// import { identifyService } from 'libp2p/identify'
import { identify } from '@libp2p/identify'
// import { circuitRelayServer, circuitRelayTransport } from 'libp2p/circuit-relay'
import { circuitRelayServer, circuitRelayTransport } from '@libp2p/circuit-relay-v2'
import { gossipsub } from '@chainsafe/libp2p-gossipsub'
import { webSockets } from '@libp2p/websockets'
import { publicIpv4 } from 'public-ip'
import { multiaddr } from '@multiformats/multiaddr'
import { webRTC } from '@libp2p/webrtc'
import { keychain } from '@libp2p/keychain'
import { defaultLogger } from '@libp2p/logger'
import { unixfs } from '@helia/unixfs'
import CreateHeliaNode from 'helia-coord/create-helia-node'
// Local libraries
import config from '../../../config/index.js'
@@ -39,59 +18,44 @@ import JsonFiles from '../json-files.js'
// https://blog.logrocket.com/alternatives-dirname-node-js-es-modules/
import * as url from 'url'
const __dirname = url.fileURLToPath(new URL('.', import.meta.url))
// console.log('__dirname: ', __dirname)
const ROOT_DIR = `${__dirname}../../../`
const IPFS_DIR = `${__dirname}../../../.ipfsdata/ipfs`
class IpfsAdapter {
constructor (localConfig) {
// Encapsulate dependencies
this.config = config
this.fs = fs
this.createLibp2p = createLibp2p
this.publicIp = publicIpv4
this.multiaddr = multiaddr
this.jsonFiles = new JsonFiles()
this.keychain = keychain
this.createHelia = createHelia
// Properties of this class instance.
this.isReady = false
// Bind 'this' object to all subfunctions
this.start = this.start.bind(this)
this.createNode = this.createNode.bind(this)
this.stop = this.stop.bind(this)
this.ensureBlocksDir = this.ensureBlocksDir.bind(this)
this.getSeed = this.getSeed.bind(this)
this.getKeychain = this.getKeychain.bind(this)
}
// Start an IPFS node.
async start () {
try {
// Ensure the directory structure exists that is needed by the IPFS node to store data.
this.ensureBlocksDir()
// Create the helia-coord node factory with production configuration.
this.heliaNode = new CreateHeliaNode({
ipfsDir: IPFS_DIR,
tcpPort: this.config.ipfsTcpPort,
wsPort: this.config.ipfsWsPort,
isCircuitRelay: this.config.isCircuitRelay,
getSeed: this.getSeed
})
// Create an IPFS node
const ipfs = await this.createNode()
// console.log('ipfs: ', ipfs)
// Start the IPFS node (creates dirs, libp2p, helia, detects public IP)
const ipfs = await this.heliaNode.start()
this.id = this.heliaNode.id
this.multiaddrs = this.heliaNode.multiaddrs
this.id = ipfs.libp2p.peerId.toString()
console.log('IPFS ID: ', this.id)
// Attempt to guess our ip4 IP address.
const ip4 = await this.publicIp()
let detectedMultiaddr = `/ip4/${ip4}/tcp/${this.config.ipfsTcpPort}/p2p/${this.id}`
detectedMultiaddr = this.multiaddr(detectedMultiaddr)
// Get the multiaddrs for the node.
const multiaddrs = ipfs.libp2p.getMultiaddrs()
multiaddrs.push(detectedMultiaddr)
console.log('Multiaddrs: ', multiaddrs)
this.multiaddrs = multiaddrs
console.log('Multiaddrs: ', this.multiaddrs)
// Signal that this adapter is ready.
this.isReady = true
@@ -101,137 +65,6 @@ class IpfsAdapter {
return this.ipfs
} catch (err) {
console.error('Error in ipfs.js/start()')
// If IPFS crashes because the /blocks directory is full, wipe the directory.
// if (err.message.includes('No space left on device')) {
// this.rmBlocksDir()
// }
throw err
}
}
async getKeychain (datastore) {
const keychainInit = {
pass: await this.getSeed()
}
const chain = this.keychain(keychainInit)({
datastore,
logger: defaultLogger()
})
return chain
}
// This function creates an IPFS node using Helia.
// It returns the node as an object.
async createNode () {
try {
// Create block and data stores.
const blockstore = new FsBlockstore(`${IPFS_DIR}/blockstore`)
const datastore = new FsDatastore(`${IPFS_DIR}/datastore`)
// const keychainInit = {
// pass: await this.getSeed()
// }
// Create an identity
let peerId
// console.log('this.keychain: ', this.keychain)
// const chain = this.keychain(keychainInit)({
// datastore,
// logger: defaultLogger()
// })
const chain = await this.getKeychain(datastore)
try {
peerId = await chain.exportPeerId('myKey')
} catch (err) {
await chain.createKey('myKey', 'Ed25519', 4096)
peerId = await chain.exportPeerId('myKey')
}
// Configure services
const services = {
identify: identify(),
pubsub: gossipsub({ allowPublishToZeroPeers: true })
}
if (this.config.isCircuitRelay) {
console.log('Helia (IPFS) node IS configured as Circuit Relay')
services.relay = circuitRelayServer({ // makes the node function as a relay server
hopTimeout: 30 * 1000, // incoming relay requests must be resolved within this time limit
advertise: true,
reservations: {
maxReservations: 15, // how many peers are allowed to reserve relay slots on this server
reservationClearInterval: 300 * 1000, // how often to reclaim stale reservations
applyDefaultLimit: true, // whether to apply default data/duration limits to each relayed connection
defaultDurationLimit: 2 * 60 * 1000, // the default maximum amount of time a relayed connection can be open for
defaultDataLimit: BigInt(2 << 7), // the default maximum number of bytes that can be transferred over a relayed connection
maxInboundHopStreams: 32, // how many inbound HOP streams are allow simultaneously
maxOutboundHopStreams: 64 // how many outbound HOP streams are allow simultaneously
}
})
} else {
console.log('Helia (IPFS) node IS NOT configured as Circuit Relay')
}
// Configure transports
let transports
if (process.env.CONNECT_PREF === 'direct') {
transports = [
tcp(),
webSockets(),
webRTC()
]
} else {
transports = [
tcp(),
webSockets(),
circuitRelayTransport({
discoverRelays: 3,
reservationConcurrency: 3
}),
webRTC()
]
}
// libp2p is the networking layer that underpins Helia
const libp2p = await this.createLibp2p({
peerId,
datastore,
addresses: {
listen: [
'/ip4/127.0.0.1/tcp/0',
`/ip4/0.0.0.0/tcp/${this.config.ipfsTcpPort}`,
`/ip4/0.0.0.0/tcp/${this.config.ipfsWsPort}/ws`,
'/webrtc'
]
},
transports,
connectionEncryption: [
noise()
],
streamMuxers: [
yamux()
],
services
})
// create a Helia node
const helia = await this.createHelia({
blockstore,
datastore,
libp2p
})
// Attach IPFS file system.
const fs = unixfs(helia)
helia.fs = fs
return helia
} catch (err) {
console.error('Error creating Helia node: ', err)
throw err
}
}
@@ -242,25 +75,6 @@ class IpfsAdapter {
return true
}
// Ensure that the directories exist to store blocks from the IPFS network.
// This function is called at startup, before the IPFS node is started.
ensureBlocksDir () {
try {
!this.fs.existsSync(`${ROOT_DIR}.ipfsdata`) && this.fs.mkdirSync(`${ROOT_DIR}.ipfsdata`)
!this.fs.existsSync(`${IPFS_DIR}`) && this.fs.mkdirSync(`${IPFS_DIR}`)
!this.fs.existsSync(`${IPFS_DIR}/blockstore`) && this.fs.mkdirSync(`${IPFS_DIR}/blockstore`)
!this.fs.existsSync(`${IPFS_DIR}/datastore`) && this.fs.mkdirSync(`${IPFS_DIR}/datastore`)
return true
} catch (err) {
console.error('Error in adapters/ipfs.js/ensureBlocksDir(): ', err)
throw err
}
}
// This function opens the seed used to generate the key for this IPFS peer.
// The seed is stored in a JSON file. If it doesn't exist, a new one is created.
async getSeed () {
@@ -280,8 +94,6 @@ class IpfsAdapter {
await this.jsonFiles.writeJSON(seed, filename)
}
// console.log('getSeed() seed: ', seed)
return seed
} catch (err) {
console.error('Error in adapters/ipfs/ipfs.js/getSeed()')
+2
View File
@@ -4,11 +4,13 @@
// Load Mongoose models.
import Users from './models/users.js'
import Usage from './models/usage.js'
class LocalDB {
constructor () {
// Encapsulate dependencies
this.Users = Users
this.Usage = Usage
}
}
+15
View File
@@ -0,0 +1,15 @@
/*
Usage data model
*/
// Global npm libraries
import mongoose from 'mongoose'
const Usage = new mongoose.Schema({
ip: { type: String },
url: { type: String },
method: { type: String },
timestamp: { type: Date }
})
export default mongoose.model('usage', Usage)
+7
View File
@@ -109,6 +109,9 @@ class P2wdbAdapter {
// Choose the BCH wallet service to use.
async selectProvider (providerId) {
try {
if (!providerId || typeof providerId !== 'string') {
throw new Error('providerId must be a string!')
}
this.ipfs.ipfsCoordAdapter.config.preferredP2wdbProvider = providerId
return true
@@ -215,6 +218,10 @@ class P2wdbAdapter {
// Returns a promise that resolves to data when the RPC response is recieved.
async waitForRPCResponse (rpcId) {
try {
if (!rpcId) {
throw new Error('rpcId can not be false or undefined')
}
// Initialize variables for tracking the return data.
let dataFound = false
let cnt = 0
+7
View File
@@ -20,6 +20,13 @@ class Wallet {
// Bind 'this' object to all subfunctions
this.instanceWalletWithoutInitialization = this.instanceWalletWithoutInitialization.bind(this)
this._instanceWallet = this._instanceWallet.bind(this)
this.openWallet = this.openWallet.bind(this)
this.instanceWallet = this.instanceWallet.bind(this)
this.incrementNextAddress = this.incrementNextAddress.bind(this)
this.getKeyPair = this.getKeyPair.bind(this)
this.optimize = this.optimize.bind(this)
this.getBalance = this.getBalance.bind(this)
}
// This is used for initializing the wallet, without waiting to update the wallet
+7
View File
@@ -23,6 +23,13 @@ class Controllers {
this.useCases = new UseCases(localConfig)
this.timerControllers = new TimerControllers({ adapters: this.adapters, useCases: this.useCases })
this.config = config
// Bind 'this' object to all subfunction
this.initAdapters = this.initAdapters.bind(this)
this.initUseCases = this.initUseCases.bind(this)
this.attachRESTControllers = this.attachRESTControllers.bind(this)
this.attachControllers = this.attachControllers.bind(this)
this.attachRPCControllers = this.attachRPCControllers.bind(this)
}
// Spin up any adapter libraries that have async startup needs.
+7 -1
View File
@@ -97,6 +97,7 @@ class JSONRPC {
wlogger.info(
`JSON RPC received from ${from}, ID: ${parsedData.payload.id}, type: ${parsedData.type}`
)
// console.log('parsedData: ', JSON.stringify(parsedData, null, 2))
}
// console.log(`parsedData: ${JSON.stringify(parsedData, null, 2)}`)
@@ -106,13 +107,18 @@ class JSONRPC {
// Default return string
let retObj = _this.defaultResponse()
console.log('parsedData ', parsedData)
// Forward data on to bch adapter if this is the response of a BCH query.
try {
if (parsedData.payload.result.method === 'bch') {
console.log('routing to BCH adapter')
retObj = await _this.adapters.bch.rpcHandler(parsedData)
}
if (parsedData.payload.result.method === 'file-pin') {
console.log('routing to IPFS Files adapter')
retObj = await _this.adapters.ipfsFiles.rpcHandler(parsedData)
}
} catch (err) {
/* exit quietly */
}
@@ -723,6 +723,33 @@ class BchRESTControllerLib {
}
}
/**
* @api {get} /bch/service Get the IPFS ID for the Wallet service
* @apiPermission public
* @apiName getService
* @apiGroup REST BCH
* @apiDescription Get the IPFS ID for the ipfs-bch-wallet-service node that
* this app is using to retrieve blockchain data from.
*
* @apiExample Example usage:
* curl -H "Content-Type: application/json" -X GET localhost:5015/bch/service
*
*/
async getService (ctx) {
try {
const selectedServiceProvider = this.adapters.ipfs.ipfsCoordAdapter.state.selectedServiceProvider
ctx.body = {
success: true,
selectedServiceProvider
}
} catch (err) {
// wlogger.error('Error in ipfs/controller.js/viewFile(): ', err)
console.log('Error in ipfs/controller.js/getService(): ', err)
this.handleError(ctx, err)
}
}
// DRY error handler
handleError (ctx, err) {
// If an HTTP status is specified by the buisiness logic, use that.
+5
View File
@@ -63,6 +63,7 @@ class BchRouter {
this.router.post('/utxoIsValid', this.utxoIsValid)
this.router.post('/getTokenData', this.getTokenData)
this.router.post('/getTokenData2', this.getTokenData2)
this.router.get('/service', this.getService)
// Attach the Controller routes to the Koa app.
app.use(this.router.routes())
@@ -116,6 +117,10 @@ class BchRouter {
async getTokenData2 (ctx, next) {
await _this.bchRESTController.getTokenData2(ctx, next)
}
async getService (ctx, next) {
await _this.bchRESTController.getService(ctx, next)
}
}
// module.exports = BchRouter
+19 -7
View File
@@ -15,6 +15,8 @@ import BchRESTController from './bch/index.js'
import IpfsRESTController from './ipfs/index.js'
import P2wdbRESTController from './p2wdb/index.js'
import PriceRESTController from './price/index.js'
import config from '../../../config/index.js'
import UsageRESTController from './usage/index.js'
class RESTControllers {
constructor (localConfig = {}) {
@@ -32,7 +34,11 @@ class RESTControllers {
)
}
// console.log('Controllers localConfig: ', localConfig)
// Bind 'this' object to all subfunctions.
this.attachRESTControllers = this.attachRESTControllers.bind(this)
// Encapsulate dependencies
this.config = config
}
attachRESTControllers (app) {
@@ -41,13 +47,15 @@ class RESTControllers {
useCases: this.useCases
}
// Attach the REST API Controllers associated with the /auth route
const authRESTController = new AuthRESTController(dependencies)
authRESTController.attach(app)
if (!this.config.noMongo) {
// Attach the REST API Controllers associated with the /auth route
const authRESTController = new AuthRESTController(dependencies)
authRESTController.attach(app)
// Attach the REST API Controllers associated with the /user route
const userRouter = new UserRouter(dependencies)
userRouter.attach(app)
// Attach the REST API Controllers associated with the /user route
const userRouter = new UserRouter(dependencies)
userRouter.attach(app)
}
// Attach the REST API Controllers associated with the /contact route
const contactRESTController = new ContactRESTController(dependencies)
@@ -72,6 +80,10 @@ class RESTControllers {
// Attach the REST API Controllers associated with the /price route
const priceRESTController = new PriceRESTController(dependencies)
priceRESTController.attach(app)
// Attach the REST API Controllers associated with the /usage route
const usageRESTController = new UsageRESTController(dependencies)
usageRESTController.attach(app)
}
}
+261 -3
View File
@@ -3,6 +3,7 @@
*/
// Global npm libraries
import mime from 'mime-types'
// Local libraries
import wlogger from '../../../adapters/wlogger.js'
@@ -34,13 +35,20 @@ class IpfsRESTControllerLib {
this.handleError = this.handleError.bind(this)
this.connect = this.connect.bind(this)
this.getThisNode = this.getThisNode.bind(this)
this.viewFile = this.viewFile.bind(this)
this.getService = this.getService.bind(this)
this.getFileInfo = this.getFileInfo.bind(this)
this.getPins = this.getPins.bind(this)
this.cid2json = this.cid2json.bind(this)
this.downloadFile = this.downloadFile.bind(this)
this.pinClaim = this.pinClaim.bind(this)
}
/**
* @api {get} /ipfs Get status on IPFS infrastructure
* @apiPermission public
* @apiName GetIpfsStatus
* @apiGroup REST BCH
* @apiGroup REST IPFS
*
* @apiExample Example usage:
* curl -H "Content-Type: application/json" -X GET localhost:5001/ipfs
@@ -58,7 +66,26 @@ class IpfsRESTControllerLib {
}
}
// Return information on IPFS peers this node is connected to.
/**
* @api {post} /ipfs/peers Get information on IPFS peers this node is connected to
* @apiPermission public
* @apiName GetIpfsPeers
* @apiGroup REST IPFS
*
* @apiParam {Boolean} [showAll=false] Whether to include detailed peer data
*
* @apiExample Example usage:
* curl -H "Content-Type: application/json" -X POST localhost:5001/ipfs/peers \
* -d '{"showAll": false}'
*
* @apiSuccess {Object[]} peers Array of peer objects
* @apiSuccess {String} peers[].peer Peer ID
* @apiSuccess {String} peers[].name Peer name
* @apiSuccess {String} peers[].protocol Protocol used by the peer
* @apiSuccess {String} peers[].version Peer version
* @apiSuccess {String} peers[].connectionAddr Connection address
* @apiSuccess {Object} [peers[].peerData] Detailed peer data (when showAll=true)
*/
async getPeers (ctx) {
try {
const showAll = ctx.request.body.showAll
@@ -73,6 +100,24 @@ class IpfsRESTControllerLib {
}
}
/**
* @api {post} /ipfs/relays Get data about the known Circuit Relays
* @apiPermission public
* @apiName GetIpfsRelays
* @apiGroup REST IPFS
*
* @apiDescription Returns information about Circuit Relays, both v1 and v2, that this node knows about. V2 relays are hydrated with peer data from the connected peers list.
*
* @apiExample Example usage:
* curl -H "Content-Type: application/json" -X POST localhost:5001/ipfs/relays
*
* @apiSuccess {Object} relays Object containing relay information
* @apiSuccess {Object[]} relays.v2Relays Array of v2 Circuit Relay objects
* @apiSuccess {String} relays.v2Relays[].ipfsId IPFS ID of the relay
* @apiSuccess {String} relays.v2Relays[].name Name of the relay (hydrated from peer data)
* @apiSuccess {String} relays.v2Relays[].description Description of the relay (hydrated from peer data)
* @apiSuccess {Object[]} relays.v1Relays Array of v1 Circuit Relay configurations
*/
// Get data about the known Circuit Relays. Hydrate with data from peers list.
async getRelays (ctx) {
try {
@@ -86,6 +131,24 @@ class IpfsRESTControllerLib {
}
}
/**
* @api {post} /ipfs/connect Connect to a specific IPFS peer
* @apiPermission public
* @apiName ConnectToIpfsPeer
* @apiGroup REST IPFS
*
* @apiDescription Attempts to establish a connection to a specific IPFS peer using the provided multiaddr. Optionally returns detailed information about the connection.
*
* @apiParam {String} multiaddr Multiaddress of the peer to connect to (required)
* @apiParam {Boolean} [getDetails=false] Whether to return detailed connection information
*
* @apiExample Example usage:
* curl -H "Content-Type: application/json" -X POST localhost:5001/ipfs/connect \
* -d '{"multiaddr": "/ip4/161.35.99.207/tcp/4001/p2p/12D3KooWDtj9cfj1SKuLbDNKvKRKSsGN8qivq9M8CYpLPDpcD5pu", "getDetails": false}'
*
* @apiSuccess {Boolean} success Indicates whether the connection attempt was successful
* @apiSuccess {Object} [details] Additional connection details (when getDetails=true)
*/
async connect (ctx) {
try {
const multiaddr = ctx.request.body.multiaddr
@@ -107,7 +170,7 @@ class IpfsRESTControllerLib {
* @api {get} /ipfs/node Get a copy of the thisNode object from helia-coord
* @apiPermission public
* @apiName GetThisNode
* @apiGroup REST BCH
* @apiGroup REST IPFS
*
* @apiExample Example usage:
* curl -H "Content-Type: application/json" -X GET localhost:5001/ipfs/node
@@ -125,8 +188,202 @@ class IpfsRESTControllerLib {
}
}
/**
* @api {get} /ipfs/view/:cid Retrieve and display a file via its IPFS CID
* @apiPermission public
* @apiName GetCidView
* @apiGroup REST IPFS
*
* @apiExample Example usage:
* curl -H "Content-Type: application/json" -X GET localhost:5001/ipfs/view/bafkreieaqtdhfywyddomswogynzymukosqqgqo7lkt5lch2zwfnc55m6om
*
*/
async viewFile (ctx) {
try {
const { cid } = ctx.params
// const file = await this.adapters.ipfs.ipfs.blockstore.get(cid)
// return file
// const cid = ctx.params.cid
const { filename, readStream } = await this.useCases.ipfs.downloadCid({ cid })
// ctx.body = ctx.req.pipe(readStream)
// Lookup the mime type from the filename.
const contentType = mime.lookup(filename)
ctx.set('Content-Type', contentType)
ctx.set(
'Content-Disposition',
// 'inline; filename="' + filename + '"'
`inline; filename="${filename}"`
)
ctx.body = readStream
} catch (err) {
console.log('Error in ipfs/controller.js/viewFile(): ', err)
this.handleError(ctx, err)
}
}
/**
* @api {get} /ipfs/download/:cid Download a file via its IPFS CID
* @apiPermission public
* @apiName GetCidDownload
* @apiGroup REST IPFS
* @apiDescription Download a file via its IPFS CID. Returns a readable stream.
*
* @apiExample Example usage:
* curl -H "Content-Type: application/json" -X GET localhost:5001/ipfs/download/bafkreieaqtdhfywyddomswogynzymukosqqgqo7lkt5lch2zwfnc55m6om
*/
async downloadFile (ctx) {
try {
const { cid } = ctx.params
const { readStream } = await this.useCases.ipfs.downloadCid({ cid })
ctx.body = readStream
} catch (err) {
console.log('Error in ipfs/controller.js/downloadFile(): ', err)
this.handleError(ctx, err)
}
}
/**
* @api {get} /ipfs/service Get the IPFS ID for the File Pin service
* @apiPermission public
* @apiName GetService
* @apiGroup REST IPFS
* @apiDescription Get the IPFS ID for the ipfs-file-pin-service node that
* this app is using to retrieve file data from.
*
* @apiExample Example usage:
* curl -H "Content-Type: application/json" -X GET localhost:5015/ipfs/service
*
*/
async getService (ctx) {
try {
const selectedIpfsFileProvider = this.adapters.ipfs.ipfsCoordAdapter.state.selectedIpfsFileProvider
ctx.body = {
success: true,
selectedIpfsFileProvider
}
} catch (err) {
// wlogger.error('Error in ipfs/controller.js/getService(): ', err)
console.log('Error in ipfs/controller.js/getService(): ', err)
this.handleError(ctx, err)
}
}
/**
* @api {get} /ipfs/file-info/:cid Get file pin info about a CID
* @apiPermission public
* @apiName GetFileInfo
* @apiGroup REST IPFS
* @apiDescription Get file metadata and pin status information give a CID.
*
* @apiExample Example usage:
* curl -H "Content-Type: application/json" -X GET localhost:5015/ipfs/file-info/bafkreieaqtdhfywyddomswogynzymukosqqgqo7lkt5lch2zwfnc55m6om
*
*/
async getFileInfo (ctx) {
try {
const { cid } = ctx.params
const ipfsFiles = this.adapters.ipfsFiles
const metadata = await ipfsFiles.getFileMetadata({ cid })
console.log('getCidMetadata() metadata: ', metadata)
ctx.body = metadata
} catch (err) {
console.log('Error in ipfs/controller.js/getFileInfo(): ', err)
this.handleError(ctx, err)
}
}
/**
* @api {get} /ipfs/pins Get info on latest 20 pinned files
* @apiPermission public
* @apiName GetPins
* @apiGroup REST IPFS
* @apiDescription Returns an array of the latest 20 pinned files.
*
* @apiExample Example usage:
* curl -H "Content-Type: application/json" -X GET localhost:5015/ipfs/pins
*
*/
async getPins (ctx) {
try {
const { page } = ctx.params
const ipfsFiles = this.adapters.ipfsFiles
const pinData = await ipfsFiles.getPins({ page })
console.log('getPins() pinData: ', pinData)
ctx.body = pinData
} catch (err) {
console.log('Error in ipfs/controller.js/getPins(): ', err)
this.handleError(ctx, err)
}
}
/**
* @api {get} /ipfs/cid2json/:cid Given a CID, retrieve a JSON object
* @apiPermission public
* @apiName GetCid2Json
* @apiGroup REST IPFS
* @apiDescription Given a CID, retrieve a JSON object.
* If the CID does not resolves to a JSON file, then an error is thrown.
*
* @apiExample Example usage:
* curl -H "Content-Type: application/json" -X GET localhost:5015/ipfs/cid2json/bafkreigbgrvpagnmrqz2vhofifrqobigsxkdvnvikf5iqrkrbwrzirazhm
*
*/
async cid2json (ctx) {
try {
const { cid } = ctx.params
const json = await this.useCases.ipfs.cid2json({ cid })
ctx.body = json
} catch (err) {
console.log('Error in ipfs/controller.js/cid2json(): ', err.message)
this.handleError(ctx, err)
}
}
/**
* @api {post} /ipfs/pin-claim Process pin claim.
* @apiPermission public
* @apiName PinClaim
* @apiGroup REST IPFS
* @apiDescription Process pin claim.
*
* @apiExample Example usage:
* curl -H "Content-Type: application/json" -X POST -d '{ "proofOfBurnTxid": "be4b63156c93f58ed311d403d9f756deda9abbc81d0fef8fbe5d769538b4261c", "cid": "bafybeied3zdwdiro7fqytyha2yfband4lwcrtozmf6shynylt3kexh26dq", "claimTxid": "c71e2f2cdf8658d90c61ac6183b8ffeeb359779807b317386044705d8352f0f2", "filename": "mutable-67ccefcca67097473e78ca10.json", "address": "bitcoincash:qqs2wrahl6azn9qdyrmp9ygeejqvzr8ruv7e9m30fr" }' http://localhost:5001/ipfs/pin-claim *
*/
async pinClaim (ctx) {
try {
const body = ctx.request.body
const result = await this.useCases.ipfs.pinClaim(body)
ctx.body = result
} catch (err) {
console.log('Error in ipfs/controller.js/cid2json(): ', err.message)
this.handleError(ctx, err)
}
}
// DRY error handler
handleError (ctx, err) {
// console.log('handleError() err.status: ', err.status)
// console.log('handleError() err.message: ', err.message)
// If an HTTP status is specified by the buisiness logic, use that.
if (err.status) {
if (err.message) {
@@ -135,6 +392,7 @@ class IpfsRESTControllerLib {
ctx.throw(err.status)
}
} else {
// console.log(`handleError() err.message: ${err.message}`)
// By default use a 422 error if the HTTP status is not specified.
ctx.throw(422, err.message)
}
+7 -1
View File
@@ -56,7 +56,13 @@ class IpfsRouter {
this.router.post('/relays', this.ipfsRESTController.getRelays)
this.router.post('/connect', this.ipfsRESTController.connect)
this.router.get('/node', this.ipfsRESTController.getThisNode)
this.router.get('/view/:cid', this.ipfsRESTController.viewFile)
this.router.get('/download/:cid', this.ipfsRESTController.downloadFile)
this.router.get('/service', this.ipfsRESTController.getService)
this.router.get('/file-info/:cid', this.ipfsRESTController.getFileInfo)
this.router.get('/pins/:page', this.ipfsRESTController.getPins)
this.router.get('/cid2json/:cid', this.ipfsRESTController.cid2json)
this.router.post('/pin-claim', this.ipfsRESTController.pinClaim)
// Attach the Controller routes to the Koa app.
app.use(this.router.routes())
app.use(this.router.allowedMethods())
+20 -1
View File
@@ -28,7 +28,10 @@ class PriceRESTControllerLib {
// this.UserModel = this.adapters.localdb.Users
// this.userUseCases = this.useCases.user
// _this = this
// Bind 'this' object to all subfunctions
this.getUSD = this.getUSD.bind(this)
this.getXecPrice = this.getXecPrice.bind(this)
this.getPsffppWritePrice = this.getPsffppWritePrice.bind(this)
}
/**
@@ -89,6 +92,22 @@ class PriceRESTControllerLib {
}
}
// Get the write price in PSF tokens to pin 1MB to the PSFFPP.
// TODO: Add API docs.
// curl -X GET http://localhost:5015/price/psffpp
async getPsffppWritePrice (ctx) {
try {
const psfPrice = await this.useCases.ipfs.getWritePrice()
ctx.body = { psfPrice }
} catch (err) {
// Write out error to error log.
wlogger.error('Error in GET /price/getPsffppWritePrice', err)
this.handleError(ctx, err)
}
}
// DRY error handler
handleError (ctx, err) {
// If an HTTP status is specified by the buisiness logic, use that.
+5
View File
@@ -53,6 +53,7 @@ class PriceRouter {
// Define the routes and attach the controller.
this.router.get('/usd', this.getPrice)
this.router.get('/xecusd', this.getXecPrice)
this.router.get('/psffpp', this.getPsffppWritePrice)
// Attach the Controller routes to the Koa app.
app.use(this.router.routes())
@@ -66,6 +67,10 @@ class PriceRouter {
async getXecPrice (ctx, next) {
await _this.priceRESTController.getXecPrice(ctx, next)
}
async getPsffppWritePrice (ctx, next) {
await _this.priceRESTController.getPsffppWritePrice(ctx, next)
}
}
// module.exports = PriceRouter
@@ -0,0 +1,119 @@
/*
REST API Controller library for the /usage route
*/
// Global npm libraries
// Local libraries
import wlogger from '../../../adapters/wlogger.js'
class UsageRESTControllerLib {
constructor (localConfig = {}) {
// Dependency Injection.
this.adapters = localConfig.adapters
if (!this.adapters) {
throw new Error(
'Instance of Adapters library required when instantiating /usage REST Controller.'
)
}
this.useCases = localConfig.useCases
if (!this.useCases) {
throw new Error(
'Instance of Use Cases library required when instantiating /usage REST Controller.'
)
}
// Encapsulate dependencies
// Bind 'this' object to all subfunctions
this.getStatus = this.getStatus.bind(this)
this.getTopIps = this.getTopIps.bind(this)
this.getTopEndpoints = this.getTopEndpoints.bind(this)
this.handleError = this.handleError.bind(this)
}
/**
* @api {get} /usage Get status on IPFS infrastructure
* @apiPermission public
* @apiName GetUsageStatus
* @apiGroup REST Usage
*
* @apiExample Example usage:
* curl -H "Content-Type: application/json" -X GET localhost:5020/usage
*
*/
getStatus (ctx) {
try {
// const status = await this.adapters.ipfs.getStatus()
const status = this.useCases.usage.getRestSummary()
ctx.body = { status }
} catch (err) {
wlogger.error('Error in usage/controller.js/getStatus(): ')
// ctx.throw(422, err.message)
this.handleError(ctx, err)
}
}
/**
* @api {get} /usage/ips Get top IP addresses consuming the REST API
* @apiPermission public
* @apiName GetUsageIPs
* @apiGroup REST Usage
*
* @apiExample Example usage:
* curl -H "Content-Type: application/json" -X GET localhost:5020/usage/ips
*
*/
getTopIps (ctx) {
try {
const ips = this.useCases.usage.getTopIps()
ctx.body = { ips }
} catch (err) {
wlogger.error('Error in usage/controller.js/getTopIps(): ')
// ctx.throw(422, err.message)
this.handleError(ctx, err)
}
}
/**
* @api {get} /usage/endpoints Get top endpoints consumed from the REST API
* @apiPermission public
* @apiName GetUsageEndpoints
* @apiGroup REST Usage
*
* @apiExample Example usage:
* curl -H "Content-Type: application/json" -X GET localhost:5020/usage/endpoints
*
*/
getTopEndpoints (ctx) {
try {
const endpoints = this.useCases.usage.getTopEndpoints()
ctx.body = { endpoints }
} catch (err) {
wlogger.error('Error in usage/controller.js/getTopEndpoints(): ')
// ctx.throw(422, err.message)
this.handleError(ctx, err)
}
}
// DRY error handler
handleError (ctx, err) {
// If an HTTP status is specified by the buisiness logic, use that.
if (err.status) {
if (err.message) {
ctx.throw(err.status, err.message)
} else {
ctx.throw(err.status)
}
} else {
// By default use a 422 error if the HTTP status is not specified.
ctx.throw(422, err.message)
}
}
}
// module.exports = IpfsRESTControllerLib
export default UsageRESTControllerLib
+65
View File
@@ -0,0 +1,65 @@
/*
REST API library for the /usage route.
*/
// Public npm libraries.
import Router from 'koa-router'
// Local libraries.
import UsageRESTControllerLib from './controller.js'
import Validators from '../middleware/validators.js'
// let _this
class UsageRouter {
constructor (localConfig = {}) {
// Dependency Injection.
this.adapters = localConfig.adapters
if (!this.adapters) {
throw new Error(
'Instance of Adapters library required when instantiating IPFS REST Controller.'
)
}
this.useCases = localConfig.useCases
if (!this.useCases) {
throw new Error(
'Instance of Use Cases library required when instantiating IPFS REST Controller.'
)
}
const dependencies = {
adapters: this.adapters,
useCases: this.useCases
}
// Encapsulate dependencies.
this.usageRESTController = new UsageRESTControllerLib(dependencies)
this.validators = new Validators()
// Instantiate the router and set the base route.
const baseUrl = '/usage'
this.router = new Router({ prefix: baseUrl })
// _this = this
}
attach (app) {
if (!app) {
throw new Error(
'Must pass app object when attaching REST API controllers.'
)
}
// Define the routes and attach the controller.
this.router.get('/', this.usageRESTController.getStatus)
this.router.get('/ips', this.usageRESTController.getTopIps)
this.router.get('/endpoints', this.usageRESTController.getTopEndpoints)
// Attach the Controller routes to the Koa app.
app.use(this.router.routes())
app.use(this.router.allowedMethods())
}
}
// module.exports = BchRouter
export default UsageRouter
+12 -1
View File
@@ -10,6 +10,8 @@ import UserRESTControllerLib from './controller.js'
import Validators from '../middleware/validators.js'
import config from '../../../../config/index.js'
let _this
class UserRouter {
@@ -34,6 +36,7 @@ class UserRouter {
}
// Encapsulate dependencies.
this.config = config
this.userRESTController = new UserRESTControllerLib(dependencies)
this.validators = new Validators()
@@ -52,7 +55,7 @@ class UserRouter {
}
// Define the routes and attach the controller.
this.router.post('/', this.userRESTController.createUser)
this.router.post('/', this.createUser)
this.router.get('/', this.getAll)
this.router.get('/:id', this.getById)
this.router.put('/:id', this.updateUser)
@@ -63,6 +66,14 @@ class UserRouter {
app.use(this.router.allowedMethods())
}
async createUser (ctx, next) {
if (process.env.DISABLE_NEW_ACCOUNTS) {
await _this.validators.ensureAdmin(ctx, next)
}
await _this.userRESTController.createUser(ctx, next)
return true
}
async getAll (ctx, next) {
await _this.validators.ensureUser(ctx, next)
await _this.userRESTController.getUsers(ctx, next)
+65 -11
View File
@@ -1,6 +1,6 @@
/*
This Controller library is concerned with timer-based functions that are
kicked off periodicially.
kicked off periodically.
*/
import config from '../../config/index.js'
@@ -23,43 +23,97 @@ class TimerControllers {
this.debugLevel = localConfig.debugLevel
// Constants
this.cleanUsageInterval = 60000 * 60 // 1 hour
this.backupUsageInterval = 60000 * 10 // 10 minutes
this.rebootInterval = 60000 * 60 * 12 // 12 hours
// Encapsulate dependencies
this.config = config
// Bind 'this' object to all subfunctions.
this.exampleTimerFunc = this.exampleTimerFunc.bind(this)
// this.startTimers()
this.cleanUsage = this.cleanUsage.bind(this)
this.backupUsage = this.backupUsage.bind(this)
this.autoReboot = this.autoReboot.bind(this)
}
// Start all the time-based controllers.
startTimers () {
// Any new timer control functions can be added here. They will be started
// when the server starts.
this.optimizeWalletHandle = setInterval(this.exampleTimerFunc, 60000 * 10)
this.cleanUsageHandle = setInterval(this.cleanUsage, this.cleanUsageInterval)
this.backupUsageHandle = setInterval(this.backupUsage, this.backupUsageInterval)
// Periodically reboot this app. Helia has a slight memory leak, and occasionally
// runs into unrecoverable network issues. Rebooting is one way to overcome these
// black-box issues.
this.rebootHandle = setInterval(this.autoReboot, this.rebootInterval)
return true
}
stopTimers () {
clearInterval(this.optimizeWalletHandle)
clearInterval(this.cleanUsageHandle)
clearInterval(this.backupUsageHandle)
}
// Replace this example function with your own timer handler.
exampleTimerFunc (negativeTest) {
// Clean the usage state so that stats reflect the last 24 hours.
cleanUsage () {
try {
console.log('Example timer controller executed.')
clearInterval(this.cleanUsageHandle)
if (negativeTest) throw new Error('test error')
const now = new Date()
console.log(`cleanUsage() Timer Controller executing at ${now.toLocaleString()}`)
this.useCases.usage.cleanUsage()
this.cleanUsageHandle = setInterval(this.cleanUsage, this.cleanUsageInterval)
return true
} catch (err) {
console.error('Error in exampleTimerFunc(): ', err)
console.error('Error in time-controller.js/cleanUsage(): ', err)
this.cleanUsageHandle = setInterval(this.cleanUsage, this.cleanUsageInterval)
// Note: Do not throw an error. This is a top-level function.
return false
}
}
// Backup the usage stats to the database
async backupUsage () {
try {
clearInterval(this.backupUsageHandle)
console.log('backupUsage() Timer Controller executing at ', new Date().toLocaleString())
// Clear the database of old usage data.
await this.useCases.usage.clearUsage()
// Save the current usage snapshot to the database.
await this.useCases.usage.saveUsage()
this.backupUsageHandle = setInterval(this.backupUsage, this.backupUsageInterval)
return true
} catch (err) {
console.error('Error in time-controller.js/backupUsage(): ', err)
this.backupUsageHandle = setInterval(this.backupUsage, this.backupUsageInterval)
// Note: Do not throw an error. This is a top-level function.
return false
}
}
// This timer is used to auto-reboot the processes. If this is being run in Docker container
// (the target for production), the Docker container should automatically restart the timer.
// This is a temporary fix to the IPFS node stalling occasionally until a better fix can be
// found.
autoReboot () {
console.log('Rebooting service.')
process.exit(1)
}
}
export default TimerControllers
+8
View File
@@ -4,7 +4,10 @@
https://troutsblog.com/blog/clean-architecture
*/
// Local libraries
import UserUseCases from './user.js'
import IpfsUseCases from './ipfs-use-cases.js'
import { UsageUseCases } from './usage-use-cases.js'
class UseCases {
constructor (localConfig = {}) {
@@ -23,10 +26,15 @@ class UseCases {
// console.log('use-cases/index.js localConfig: ', localConfig)
this.user = new UserUseCases(localConfig)
this.ipfs = new IpfsUseCases(localConfig)
this.usage = new UsageUseCases(localConfig)
}
// Run any startup Use Cases at the start of the app.
async start () {
// Load the usage stats from the database
await this.usage.loadUsage()
console.log('Async Use Cases have been started.')
return true
+354
View File
@@ -0,0 +1,354 @@
/*
Use cases for working with IPFS.
*/
// Global npm libraries
// import Wallet from 'minimal-slp-wallet'
// import { CID } from 'multiformats'
// import RetryQueue from '@chris.troutner/retry-queue'
// import { exporter } from 'ipfs-unixfs-exporter'
import { Duplex } from 'stream'
import PSFFPP from 'psffpp'
import { CID } from 'multiformats'
// Local libraries
// import PinEntity from '../entities/pin.js'
// import config from '../../config/index.js'
// const PSF_TOKEN_ID = '38e97c5d7d3585a2cbf3f9580c82ca33985f9cb0845d4dcce220cb709f9538b0'
class IpfsUseCases {
constructor (localConfig = {}) {
// console.log('User localConfig: ', localConfig)
this.adapters = localConfig.adapters
if (!this.adapters) {
throw new Error(
'Instance of adapters must be passed in when instantiating IPFS Use Cases library.'
)
}
// Encapsulate dependencies
this.psffpp = null // placeholder
this.CID = CID
// Bind 'this' object to all class subfunctions.
this.downloadCid = this.downloadCid.bind(this)
// this.downloadCid2 = this.downloadCid2.bind(this)
this.getWritePrice = this.getWritePrice.bind(this)
this.getCidMetadata = this.getCidMetadata.bind(this)
this.cid2json = this.cid2json.bind(this)
this.pinClaim = this.pinClaim.bind(this)
// State
this.lastWritePriceUpdate = null // Used to periodically update write price.
}
// Download a pinned file, given its CID.
// Returns a readable stream.
async downloadCid (inObj = {}) {
try {
let { cid } = inObj
if (!cid) throw new Error('CID is undefined')
// const Pins = this.adapters.localdb.Pins
// let existingModel = await Pins.find({ cid })
// existingModel = existingModel[0]
// console.log('existingModel: ', existingModel)
// if (!existingModel) {
// throw new Error(`Database model for CID ${cid} does not exist.`)
// }
//
// if (!existingModel.dataPinned) {
// throw new Error('File has not been pinned. Not available.')
// }
const filename = await this.getCidMetadata({ cid })
const helia = this.adapters.ipfs.ipfs
const cidClass = this.CID.parse(cid)
// Download the file from other nodes.
await helia.blockstore.get(cidClass)
// list cid content, to dermine if this is a file-based or directory-based CID.
const contentArray = []
for await (const file of helia.fs.ls(cid)) {
contentArray.push(file)
}
// Handle directory-based CIDs. This is used by Token Tiger for mutable and immutable token data.
/**
* If the cid is a directory
* The next block of code sends a html page with a list of links with the file names into the directory.
* Skipping this code delivers directly the first file detected in the directory.
*/
const isDir = contentArray[0].path.match('/') // TODO : looking for a better way to detect if is a directory
// 'listDir' is a flag to ignore this code on /download endpoint.
if (isDir) {
console.log('This CID is a directory. contentArray[0]: ', contentArray[0])
cid = `${cid}/${contentArray[0].name}`
// throw new Error('CID is a directory. Not supported yet.')
// const stream = new Stream.Readable({ read () { } })
// for (let i = 0; i < contentArray.length; i++) {
// const cont = contentArray[i]
// // List all paths excluding root path.
// if (cont.path !== cid) {
// // Add links to the gateway with the format cid/:filename
// stream.push(`<a href='${this.config.domainName}/ipfs/view/${cid}/${cont.name}' >/${cont.name} ( CID: ${cont.cid} )</a><hr />`)
// }
// }
// stream.push(null)
// // return fileName as html because the controller the library <mime.lookup> sends it as html
// return { readStream: stream }
} else {
console.log('This CID is not a directory.')
}
// Convert the file to a Buffer.
const fileChunks = []
for await (const chunk of helia.fs.cat(cid)) {
fileChunks.push(chunk)
}
const fileBuf = Buffer.concat(fileChunks)
// Convert the Buffer into a readable stream
const bufferToStream = (myBuffer) => {
const tmp = new Duplex()
tmp.push(myBuffer)
tmp.push(null)
return tmp
}
const readStream = bufferToStream(fileBuf)
// const filename = 'test.jpg'
return { filename, readStream }
} catch (err) {
console.error('Error in use-cases/ipfs.js/dowloadCid()')
throw err
}
}
// Get the price (in PSF tokens) to pin 1MB to the PSFFPP network. This price
// is set on-chain by the PSF Minting Council. PSFoundation.cash
async getWritePrice (inObj = {}) {
try {
const wallet = this.adapters.wallet.bchWallet
// console.log('getWritePrice() wallet: ', wallet)
// Instantiate the psffpp library if it hasn't already been.
// Dev Note: Important to only instantiate once, since the write price
// is cached by the psffpp library. Instantiating only once improves
// performance of price lookup.
if (!this.psffpp) {
this.psffpp = new PSFFPP({ wallet })
const now = new Date()
this.lastWritePriceUpdate = now.getTime()
}
// Periodically re-validate the write-price (by re-instantiating the
// PSFFPP library) in case it's changed.
const now = new Date()
const sixHours = 1000 * 60 * 60 * 6
if (this.lastWritePriceUpdate + sixHours < now.getTime()) {
this.psffpp = new PSFFPP({ wallet })
this.lastWritePriceUpdate = now.getTime()
}
const writePrice = await this.psffpp.getMcWritePrice()
return writePrice
} catch (err) {
console.error('Error in use-cases/ipfs-use-cases.js/getWritePrice()')
throw err
}
}
// Given a CID, this function will retrieve file metadata from the
// ipfs-file-pin-service, using the IPFS JSON-RPC. This metadata includes
// the filename, which can be used to infer mime-type, so that it can be
// delivered to a web browser.
async getCidMetadata (inObj = {}) {
try {
const { cid } = inObj
const ipfsFiles = this.adapters.ipfsFiles
const metadata = await ipfsFiles.getFileMetadata({ cid })
console.log('getCidMetadata() metadata: ', metadata)
if (metadata.success === false) {
throw new Error(metadata.message)
}
const filename = metadata.fileMetadata.filename
return filename
} catch (err) {
console.error('Error in getCidMetadata(): ', err)
throw err
}
}
// Given a CID, this function will retrieve a JSON object from the
// ipfs-file-pin-service, using the IPFS JSON-RPC.
async cid2json (inObj = {}) {
try {
const { cid } = inObj
console.log('cid2json() cid: ', cid)
// Throw an error if ipfs-bch-wallet-consumer has not yet connected to an instance of ipfs-file-pin-service.
const ipfsFileProvider = this.adapters.ipfs.ipfsCoordAdapter.state.selectedIpfsFileProvider
if (!ipfsFileProvider) {
throw new Error('No IPFS File Provider Service is available yet. Try again in a few seconds.')
}
// Throw an error if ipfs-bch-wallet-consumer can not communicate with the ipfs-file-pin-service.
const ipfsFiles = this.adapters.ipfsFiles
const metadata = await ipfsFiles.getFileMetadata({ cid })
if (metadata.success === false) {
throw new Error(`Could not communicate with instance of ipfs-file-pin-service ${ipfsFileProvider}. Try again in a few seconds.`)
}
// Throw an error if the file is not pinned.
const fileMetadata = metadata.fileMetadata
if (!fileMetadata.dataPinned) {
throw new Error(`CID ${cid} has not been pinned by ipfs-file-pin-service instance ${ipfsFileProvider}`)
}
// console.log('fileMetadata: ', fileMetadata)
// Throw an error if this is not a JSON file
const filename = fileMetadata.filename
if (!filename.endsWith('.json')) {
throw new Error(`CID ${cid} does not resolve to a JSON file.`)
}
// Retrieve the CID content and store it in a Buffer.
const helia = this.adapters.ipfs.ipfs
const fileChunks = []
// list cid content. This is used to determine if the CID is a file or a directory.
const contentArray = []
for await (const file of helia.fs.ls(cid)) {
contentArray.push(file)
}
// console.log('contentArray: ', contentArray)
try {
// Handle CIDs that are files.
for await (const chunk of helia.fs.cat(cid)) {
fileChunks.push(chunk)
}
} catch (err) {
// Extra logic here to look at the filename of the first file and make sure
// it ends in .json.
const name = contentArray[0].name
if (!name.endsWith('.json')) {
throw new Error(`CID ${cid} is a directory and its first file does not resolve to a valid JSON file.`)
}
// Handle CIDs that are directorys containing a JSON file. (TokenTiger.com tokens)
for await (const chunk of helia.fs.cat(`${cid}/${name}`)) {
fileChunks.push(chunk)
}
}
const fileBuf = Buffer.concat(fileChunks)
// Convert the Buffer into a string.
const jsonStr = fileBuf.toString()
// Parse the string into a JSON object.
let json = null
try {
json = JSON.parse(jsonStr)
} catch (err) {
throw new Error(`CID ${cid} does not resolve to a valid JSON object.`)
}
return {
success: true,
json
}
} catch (err) {
console.error('Error in ipfs-use-cases.js/cid2json(): ', err.message)
throw err
}
}
async pinClaim (inObj = {}) {
try {
// Throw an error if ipfs-bch-wallet-consumer can not communicate with the ipfs-file-pin-service.
const metadata = await this.adapters.ipfsFiles.pinClaim(inObj)
return {
success: metadata.success,
message: metadata.message
}
} catch (err) {
console.error('Error in ipfs-use-cases.js/pinClaim(): ', err)
throw err
}
}
// async downloadCid2 (inObj = {}) {
// try {
// const { cid } = inObj
//
// // console.log(`downloadFile() retrieving this CID: ${cid}, with fileName: ${fileName}, and path: ${path}`)
//
// const blockstore = this.adapters.ipfs.ipfs.blockstore
// const entry = await this.exporter(cid, blockstore)
//
// console.info(entry.cid) // Qmqux
// console.log('entry: ', entry)
// // console.info(entry.unixfs.fileSize()) // 4
//
// // const filePath = `${path}/${fileName}`
// // console.log(`filePath: ${filePath}`)
// // const writableStream = this.fs.createWriteStream(filePath)
// //
// // writableStream.on('error', this.writeStreamError)
// //
// // writableStream.on('finish', this.writeStreamFinished)
// //
//
// const fileChunks = []
// for await (const buf of entry.content()) {
// fileChunks.push(buf)
// }
// const fileBuf = Buffer.concat(fileChunks)
//
// //
// // writableStream.end()
//
// // Convert the Buffer into a readable stream
// const bufferToStream = (myBuffer) => {
// const tmp = new Duplex()
// tmp.push(myBuffer)
// tmp.push(null)
// return tmp
// }
// const readStream = bufferToStream(fileBuf)
//
// const filename = 'test.jpg'
//
// return { filename, readStream }
//
// // return { cid }
// } catch (err) {
// console.error('Error in ipfs-use-cases.js/downloadCid()')
// throw err
// }
// }
}
export default IpfsUseCases
+208
View File
@@ -0,0 +1,208 @@
/*
Use Case library for tracking usage. This library contains business logic
for tracking the usage of REST API and JSON RPC calls. This library is used
by admins to keep an eye on how many API calls were made in a 24-hour and
1-hour time period.
Usage stats are held in memory. But they are periodically backed up to the
Mongo database. On startup, the usage stats are loaded from the database.
This allows the usage stats to be persisted across restarts.
*/
// This global variable is used to share data between the REST middleware and
// the Usage Use Case class instance.
let restCalls = []
class UsageUseCases {
constructor (localConfig = {}) {
// console.log('User localConfig: ', localConfig)
this.adapters = localConfig.adapters
if (!this.adapters) {
throw new Error(
'Instance of adapters must be passed in when instantiating Usage Use Cases library.'
)
}
// Encapsulate dependencies
this.UsageModel = this.adapters.localdb.Usage
// Bind 'this' object to all subfunctions
this.cleanUsage = this.cleanUsage.bind(this)
this.getRestSummary = this.getRestSummary.bind(this)
this.getTopIps = this.getTopIps.bind(this)
this.getTopEndpoints = this.getTopEndpoints.bind(this)
this.clearUsage = this.clearUsage.bind(this)
this.saveUsage = this.saveUsage.bind(this)
this.loadUsage = this.loadUsage.bind(this)
// State
}
// Clean up the state by removing entries that are older than 24 hours. This
// ensures stats reflect only the last 24 hours.
// This function is called by a Timer Controller.
cleanUsage () {
try {
const now = new Date()
const twentyFourHoursAgo = now.getTime() - (60000 * 60 * 24)
console.log('cleanUsage() now: ', now)
console.log('cleanUsage() restCalls.length before filtering: ', restCalls.length)
restCalls = restCalls.filter(x => x.timestamp > twentyFourHoursAgo)
console.log('cleanUsage() restCalls.length after filtering: ', restCalls.length)
if (!restCalls) restCalls = []
return restCalls
} catch (err) {
console.error('Error in usage-use-cases.js/cleanUsage()')
throw err
}
}
// Track the calls to a REST API
getRestSummary (inObj = {}) {
try {
console.log(`getRestSummary(): There have been ${restCalls.length} REST calls`)
return restCalls.length
} catch (err) {
console.error('Error in usage-use-cases.js/getRestSummary()')
throw err
}
}
// Get the top 20 IP addresses from the stats.
getTopIps () {
try {
const ips = restCalls.map(x => x.ip)
// Create a Map to count occurrences of each IP address string
const countMap = new Map()
ips.forEach(ip => {
countMap.set(ip, (countMap.get(ip) || 0) + 1)
})
// Convert the Map into an array of objects with `str` and `cnt` properties
const result = Array.from(countMap, ([ip, cnt]) => ({ ip, cnt }))
// Sort the results by the `cnt` property in descending order
result.sort((a, b) => b.cnt - a.cnt)
// Ensure the result has at most 20 elements
return result.slice(0, 20)
} catch (err) {
console.error('Error in usage-use-cases.js/getTopIps()')
throw err
}
}
// Get the top 20 most consumed endpoints.
getTopEndpoints () {
try {
const endpoints = restCalls.map(x => `${x.method} ${x.url}`)
// Create a Map to count occurrences of each IP address string
const countMap = new Map()
endpoints.forEach(endpoint => {
countMap.set(endpoint, (countMap.get(endpoint) || 0) + 1)
})
// Convert the Map into an array of objects with `str` and `cnt` properties
const result = Array.from(countMap, ([endpoint, cnt]) => ({ endpoint, cnt }))
// Sort the results by the `cnt` property in descending order
result.sort((a, b) => b.cnt - a.cnt)
// Ensure the result has at most 20 elements
return result.slice(0, 20)
} catch (err) {
console.error('Error in usage-use-cases.js/getTopEndpoints()')
throw err
}
}
// Clear the usage database data
async clearUsage () {
try {
await this.UsageModel.deleteMany({})
// Debugging: verify the database is empty
// Delete this code after debugging
const usage = await this.UsageModel.find({})
console.log('clearUsage() usage: ', usage)
return true
} catch (err) {
console.error('Error in usage-use-cases.js/clearUsage()')
throw err
}
}
// Save the usage data to the database
async saveUsage (inObj = {}) {
try {
for (let i = 0; i < restCalls.length; i++) {
const thisRestCall = restCalls[i]
const usageData = {
ip: thisRestCall.ip,
url: thisRestCall.url,
method: thisRestCall.method,
timestamp: thisRestCall.timestamp
}
const usage = new this.UsageModel(usageData)
await usage.save()
}
return true
} catch (err) {
console.error('Error in usage-use-cases.js/saveUsage()')
throw err
}
}
// Load usage data from the database
async loadUsage () {
try {
const usage = await this.UsageModel.find({})
// console.log('usage: ', usage)
restCalls = usage
if (!restCalls) restCalls = []
return usage
} catch (err) {
console.error('Error in usage-use-cases.js/loadUsage(): ', err)
return false
// throw err
}
}
}
// This Koa middleware is called any time there is a REST API. It logs the
// details from the request object.
function usageMiddleware () {
return async (ctx, next) => {
try {
await next()
// console.log('ctx.request: ', ctx.request)
const now = new Date()
const reqObj = {
ip: ctx.request.ip,
url: ctx.request.url,
method: ctx.request.method,
timestamp: now.getTime()
}
// console.log('reqObj: ', reqObj)
restCalls.push(reqObj)
} catch (err) {
ctx.status = err.status || 500
ctx.body = err.message
ctx.app.emit('error', err, ctx)
}
}
};
export { UsageUseCases, usageMiddleware, restCalls }
@@ -0,0 +1,45 @@
/*
Liveness test runs before all other tests to ensure the server is running.
*/
// Public npm libraries
import { assert } from 'chai'
import axios from 'axios'
import testUtils from '../../utils/test-utils.js'
// const sinon = require('sinon')
// Local support libraries
import config from '../../../config/index.js'
// import Server from '../../../bin/server.js'
// import testUtils from '../../utils/test-utils.js'
// const adminLib = new AdminLib()
const LOCALHOST = `http://localhost:${config.port}`
describe('#Check Server Liveness', () => {
// before(async () => {
it('should confirm the server is running', async () => {
try {
const response = await axios.get(`${LOCALHOST}/`)
assert(response.status === 200, 'Server is running, continuing with E2E tests.')
} catch (err) {
console.log('\nServer is not running, exiting tests.')
console.log('Start the server with `npm run start:e2e:server` before running E2E tests.\n')
console.log('Ensure running npm run docs before running the test server')
process.exit(1)
}
})
it('should confirm the server is running over test enviroment', async () => {
try {
const res = await testUtils.loginAdminUser()
assert.property(res, 'user')
assert.property(res, 'token')
assert.property(res, 'id')
} catch (err) {
console.log('\nServer is not running over test enviroment, exiting tests.')
console.log('Start the server with `npm run start:e2e:server` before running E2E tests.\n')
process.exit(1)
}
})
})
+89 -91
View File
@@ -13,116 +13,114 @@ import axios from 'axios'
// Local support libraries
import config from '../../../config/index.js'
import Server from '../../../bin/server.js'
// import Server from '../../../bin/server.js'
import testUtils from '../../utils/test-utils.js'
import AdminLib from '../../../src/adapters/admin.js'
const adminLib = new AdminLib()
// const request = supertest.agent(app.listen())
const context = {}
const LOCALHOST = `http://localhost:${config.port}`
describe('Auth', () => {
before(async () => {
const app = new Server()
if (!config.noMongo) {
describe('Auth', () => {
before(async () => {
// const app = new Server()
// This should be the first instruction. It starts the REST API server.
await app.startServer()
// This should be the first instruction. It starts the REST API server.
// await app.startServer()
// Stop the IPFS node for the rest of the e2e tests.
// await app.controllers.adapters.ipfs.stop()
// TODO:
// Delete all previous users in the database.
await testUtils.deleteAllUsers()
// Stop the IPFS node for the rest of the e2e tests.
// await app.controllers.adapters.ipfs.stop()
// Create a new admin user.
await adminLib.createSystemUser()
// Delete all previous users in the database.
await testUtils.deleteAllUsers()
const userObj = {
email: 'test@test.com',
password: 'pass',
name: 'test'
}
const testUser = await testUtils.createUser(userObj)
// console.log('TestUser: ', testUser)
context.user = testUser.user
context.token = testUser.token
})
describe('POST /auth', () => {
it('should throw 401 if credentials are incorrect', async () => {
try {
const options = {
method: 'post',
url: `${LOCALHOST}/auth`,
data: {
email: 'test@test.com',
password: 'wrongpassword'
}
}
const result = await axios(options)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
console.log(
`result stringified: ${JSON.stringify(result.data, null, 2)}`
)
assert(false, 'Unexpected result')
} catch (err) {
assert(err.response.status === 401, 'Error code 401 expected.')
const userObj = {
email: 'test@test.com',
password: 'pass',
name: 'test'
}
const testUser = await testUtils.createUser(userObj)
// console.log('TestUser: ', testUser)
context.user = testUser.user
context.token = testUser.token
})
it('should throw 401 if email is wrong format', async () => {
try {
const options = {
method: 'post',
url: `${LOCALHOST}/auth`,
data: {
email: 'wrongEmail',
password: 'wrongpassword'
describe('POST /auth', () => {
it('should throw 401 if credentials are incorrect', async () => {
try {
const options = {
method: 'post',
url: `${LOCALHOST}/auth`,
data: {
email: 'test@test.com',
password: 'wrongpassword'
}
}
const result = await axios(options)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
console.log(
`result stringified: ${JSON.stringify(result.data, null, 2)}`
)
assert(false, 'Unexpected result')
} catch (err) {
assert(err.response.status === 401, 'Error code 401 expected.')
}
})
await axios(options)
assert(false, 'Unexpected result')
} catch (err) {
assert(err.response.status === 401, 'Error code 401 expected.')
}
})
it('should auth user', async () => {
try {
const options = {
method: 'post',
url: `${LOCALHOST}/auth`,
data: {
email: 'test@test.com',
password: 'pass'
it('should throw 401 if email is wrong format', async () => {
try {
const options = {
method: 'post',
url: `${LOCALHOST}/auth`,
data: {
email: 'wrongEmail',
password: 'wrongpassword'
}
}
}
const result = await axios(options)
// console.log(`result: ${JSON.stringify(result.data, null, 2)}`)
assert(result.status === 200, 'Status Code 200 expected.')
assert(
result.data.user.email === 'test@test.com',
'Email of test expected'
)
assert(
result.data.user.password === undefined,
'Password expected to be omited'
)
} catch (err) {
console.log(
'Error authenticating test user: ' + JSON.stringify(err, null, 2)
)
throw err
}
await axios(options)
assert(false, 'Unexpected result')
} catch (err) {
assert(err.response.status === 401, 'Error code 401 expected.')
}
})
it('should auth user', async () => {
try {
const options = {
method: 'post',
url: `${LOCALHOST}/auth`,
data: {
email: 'test@test.com',
password: 'pass'
}
}
const result = await axios(options)
// console.log(`result: ${JSON.stringify(result.data, null, 2)}`)
assert(result.status === 200, 'Status Code 200 expected.')
assert(
result.data.user.email === 'test@test.com',
'Email of test expected'
)
assert(
result.data.user.password === undefined,
'Password expected to be omited'
)
} catch (err) {
console.log(
'Error authenticating test user: ' + JSON.stringify(err, null, 2)
)
throw err
}
})
})
})
})
}
File diff suppressed because it is too large Load Diff
-115
View File
@@ -1,115 +0,0 @@
import { assert } from 'chai'
import Admin from '../../../src/adapters/admin.js'
import sinon from 'sinon'
import util from 'util'
util.inspect.defaultOptions = { depth: 1 }
let sandbox
let uut
describe('Admin', () => {
beforeEach(() => {
uut = new Admin()
sandbox = sinon.createSandbox()
})
afterEach(() => sandbox.restore())
describe('loginAdmin()', () => {
it('should logind admin', async () => {
try {
const error = new Error('test error')
error.response = {
status: 422
}
// sandbox.stub(uut.axios, 'request').onFirstCall().throws(error)
const result = await uut.loginAdmin()
const user = result.data.user
assert.property(user, '_id')
assert.property(user, 'email')
assert.property(user, 'type')
assert.isString(user._id)
assert.isString(user.email)
assert.isString(user.type)
assert.equal(user.type, 'admin')
} catch (err) {
assert(false, 'Unexpected result')
}
})
it('should handle axios error', async () => {
try {
// Returns an erroneous password to force
// an auth error
sandbox.stub(uut.jsonFiles, 'readJSON').resolves({ password: 'wrong' })
await uut.loginAdmin()
assert(false, 'Unexpected result')
} catch (err) {
assert.equal(err.response.status, 401)
assert.include(err.response.data, 'Unauthorized')
}
})
})
describe('createSystemUser()', () => {
it('should create admin', async () => {
try {
const result = await uut.createSystemUser()
assert.property(result, 'email')
assert.property(result, 'password')
assert.property(result, 'id')
assert.property(result, 'token')
} catch (err) {
assert(false, 'Unexpected result')
}
})
it('should handle axios error', async () => {
try {
const error1 = new Error('test error')
error1.response = {
status: 422
}
const error2 = new Error('test error')
error1.response = {
status: 500
}
// The loginAdmin() function in some use cases is recursive
// after handling the 422 error, it gets called again
sandbox
.stub(uut.axios, 'request')
.onFirstCall()
.throws(error1)
.onSecondCall()
.throws(error2)
await uut.createSystemUser()
assert(false, 'Unexpected result')
} catch (err) {
assert.include(err.message, 'test error')
}
})
it('should handle errors when remove user', async () => {
try {
const error1 = new Error('test error')
error1.response = {
status: 422
}
sandbox.stub(uut.axios, 'request').throws(error1)
sandbox.stub(uut.User, 'deleteOne').throws(new Error('test error'))
await uut.createSystemUser()
assert(false, 'Unexpected result')
} catch (err) {
assert.include(err.message, 'test error')
}
})
})
})
+74
View File
@@ -0,0 +1,74 @@
/*
End-to-end tests for /usage endpoints.
*/
import config from '../../../config/index.js'
import { assert } from 'chai'
import axios from 'axios'
import sinon from 'sinon'
import util from 'util'
util.inspect.defaultOptions = { depth: 1 }
const LOCALHOST = `http://localhost:${config.port}`
let sandbox
describe('Usage', () => {
beforeEach(() => {
sandbox = sinon.createSandbox()
})
afterEach(() => sandbox.restore())
describe('GET /usage', () => {
it('should return usage status', async () => {
try {
const options = {
method: 'get',
url: `${LOCALHOST}/usage`
}
const result = await axios(options)
assert.property(result.data, 'status')
} catch (err) {
assert(false, 'Unexpected result')
}
})
})
describe('GET /usage/ips', () => {
it('should return ips', async () => {
try {
const options = {
method: 'get',
url: `${LOCALHOST}/usage/ips`
}
const result = await axios(options)
assert.property(result.data, 'ips')
} catch (err) {
assert(false, 'Unexpected result')
}
})
})
describe('GET /usage/endpoints', () => {
it('should return ips', async () => {
try {
const options = {
method: 'get',
url: `${LOCALHOST}/usage/endpoints`
}
const result = await axios(options)
assert.property(result.data, 'endpoints')
} catch (err) {
assert(false, 'Unexpected result')
}
})
})
})
+19 -3
View File
@@ -14,13 +14,14 @@ describe('#adapters', () => {
let uut, sandbox
beforeEach(() => {
uut = new Adapters()
sandbox = sinon.createSandbox()
uut = new Adapters()
})
afterEach(() => {
sandbox.restore()
if (sandbox) {
sandbox.restore()
}
})
describe('#start', () => {
@@ -37,6 +38,21 @@ describe('#adapters', () => {
assert.equal(result, true)
})
it('should not start ipfs on test enviroment', async () => {
// Mock dependencies
uut.config.getJwtAtStartup = true
uut.config.useIpfs = true
uut.config.env = 'test'
sandbox.stub(uut.fullStackJwt, 'getJWT').resolves()
sandbox.stub(uut.fullStackJwt, 'instanceBchjs').resolves()
const ipfsSpy = sandbox.stub(uut.ipfs, 'start').resolves(null)
const result = await uut.start()
assert.isTrue(ipfsSpy.notCalled)
assert.equal(result, true)
})
it('should catch and throw an error', async () => {
try {
+122
View File
@@ -0,0 +1,122 @@
import { assert } from 'chai'
import Admin from '../../../src/adapters/admin.js'
import sinon from 'sinon'
import util from 'util'
import config from '../../../config/index.js'
util.inspect.defaultOptions = { depth: 1 }
let sandbox
let uut
describe('Admin', () => {
beforeEach(() => {
uut = new Admin()
sandbox = sinon.createSandbox()
})
afterEach(() => sandbox.restore())
if (!config.noMongo) {
describe('loginAdmin()', () => {
it('should login admin', async () => {
try {
sandbox.stub(uut.jsonFiles, 'readJSON').resolves({ password: 'pass' })
sandbox.stub(uut.axios, 'request').resolves(true)
const result = await uut.loginAdmin()
assert.isTrue(result)
} catch (err) {
assert(false, 'Unexpected result')
}
})
it('should handle axios error', async () => {
try {
// Returns an erroneous password to force
// an auth error
sandbox.stub(uut.axios, 'request').throws(new Error('test error'))
sandbox.stub(uut.jsonFiles, 'readJSON').resolves({ password: 'wrong' })
await uut.loginAdmin()
assert(false, 'Unexpected result')
} catch (err) {
assert.include(err.message, 'test error')
}
})
})
describe('createSystemUser()', () => {
it('should create admin', async () => {
try {
await uut.deleteExistingSystemUser()
const result = await uut.createSystemUser()
assert.property(result, 'email')
assert.property(result, 'password')
assert.property(result, 'id')
assert.property(result, 'token')
} catch (err) {
assert(false, 'Unexpected result')
}
})
it('should update admin password', async () => {
try {
uut.config.adminPassword = 'newpassword'
const fakeUser = {
password: 'oldpassword',
save: () => { return 'token' },
generateToken: () => { return 'token' }
}
sandbox.stub(uut.User, 'findOne').resolves(fakeUser)
sandbox.stub(uut.jsonFiles, 'writeJSON').resolves(true)
const result = await uut.createSystemUser()
assert.property(result, 'email')
assert.property(result, 'password')
assert.property(result, 'id')
assert.property(result, 'token')
assert.equal(fakeUser.password, 'newpassword', 'password should be updated')
} catch (err) {
console.log(err)
assert(false, 'Unexpected result')
}
})
it('should handle error', async () => {
try {
sandbox.stub(uut.User, 'findOne').throws(new Error('test error'))
await uut.createSystemUser()
assert.fail('Unexpected result')
} catch (err) {
assert.include(err.message, 'test error')
}
})
})
describe('deleteExistingSystemUser()', () => {
it('should delete admin', async () => {
try {
sandbox.stub(uut.User, 'deleteOne').resolves(true)
const result = await uut.deleteExistingSystemUser()
assert.isTrue(result)
} catch (err) {
assert(false, 'Unexpected result')
}
})
it('should handle error when deleting admin', async () => {
try {
sandbox.stub(uut.User, 'deleteOne').throws(new Error('test error'))
await uut.deleteExistingSystemUser()
assert.fail('Unexpected result')
} catch (err) {
assert.include(err.message, 'test error')
}
})
})
}
})
+186
View File
@@ -17,6 +17,7 @@ import cloneDeep from 'lodash.clonedeep'
import BchAdapter from '../../../src/adapters/bch/index.js'
import adapters from '../mocks/adapters/index.js'
import mockDataLib from '../mocks/use-cases/rest-api-mocks.js'
import ipfsCoordMocks from '../mocks/adapters/ipfs-coord-mocks.js'
const eventEmitter = new EventEmitter()
@@ -91,7 +92,30 @@ describe('#bch-use-case', () => {
assert.property(result, 'balances')
assert.isArray(result.balances)
})
it('should return false if data is not found', async () => {
// Mock dependencies
uut.ipfs.ipfsCoordAdapter.wallet = {
bchjs: {
Util: {
sleep: () => {}
}
}
}
// Mock data.
const rpcId = '123'
// Fill the queue with false values.
for (let i = 0; i < 10; i++) {
uut.rpcDataQueue.push({ payload: { id: '1234567890' } })
}
sandbox.stub(uut, 'sleep').resolves()
const result = await uut.waitForRPCResponse(rpcId)
// console.log('result: ', result)
assert.property(result, 'success')
assert.isFalse(result.success)
})
it('should catch and throw an error', async () => {
try {
await uut.waitForRPCResponse()
@@ -114,12 +138,46 @@ describe('#bch-use-case', () => {
uut.rpcHandler(data)
})
it('should skip errors', async () => {
try {
sandbox.stub(uut, 'rpcDataQueue').throws(new Error('test error'))
await uut.rpcHandler()
} catch (err) {
assert.fail('Unexpected code path')
}
})
})
describe('#getStatus', () => {
it('should get the status from ipfs-coord', async () => {
uut.ipfs.ipfsCoordAdapter.state.serviceProviders = ipfsCoordMocks.peers
uut.ipfs.ipfsCoordAdapter.ipfsCoord.thisNode.peerData = ipfsCoordMocks.peerData
const result = await uut.getStatus()
console.log('result: ', result)
assert.isObject(result)
})
it('should handle error', async () => {
try {
uut.ipfs.ipfsCoordAdapter.ipfsCoord.thisNode = null
await uut.getStatus()
assert.fail('Unexpected code path')
} catch (err) {
assert.include(err.message, 'Cannot read')
}
})
})
describe('#selectProvider', () => {
it('should select provider', async () => {
await uut.selectProvider('providerId to select')
assert.equal(uut.ipfs.ipfsCoordAdapter.config.preferredProvider, 'providerId to select')
})
it('should handle error', async () => {
try {
await uut.selectProvider()
assert.fail('Unexpected code path')
} catch (err) {
assert.include(err.message, 'providerId must be a string!')
}
})
})
@@ -154,6 +212,18 @@ describe('#bch-use-case', () => {
assert.equal(err.message, 'test error')
}
})
it('should throw an error if selected provider is not defined', async () => {
try {
uut.ipfs.ipfsCoordAdapter.state.selectedServiceProvider = null
await uut.getBalances()
assert.fail('Unexpected code path')
} catch (err) {
// console.log(err)
assert.equal(err.message, 'No BCH Wallet Service provider available yet.')
}
})
})
describe('#getUtxos', () => {
@@ -184,6 +254,19 @@ describe('#bch-use-case', () => {
assert.equal(err.message, 'addr required when calling getUtxos')
}
})
it('should throw an error if selected provider is not defined', async () => {
try {
uut.ipfs.ipfsCoordAdapter.state.selectedServiceProvider = null
const addr = 'addr'
await uut.getUtxos(addr)
assert.fail('Unexpected code path')
} catch (err) {
// console.log(err)
assert.equal(err.message, 'No BCH Wallet Service provider available yet.')
}
})
})
describe('#getUtxosBulk', () => {
@@ -230,6 +313,19 @@ describe('#bch-use-case', () => {
assert.equal(err.message, 'addresses parameter must not exceed 20 elements')
}
})
it('should throw an error if selected provider is not defined', async () => {
try {
uut.ipfs.ipfsCoordAdapter.state.selectedServiceProvider = null
const addrs = ['addr']
await uut.getUtxosBulk(addrs)
assert.fail('Unexpected code path')
} catch (err) {
// console.log(err)
assert.equal(err.message, 'No BCH Wallet Service provider available yet.')
}
})
})
describe('#broadcast', () => {
@@ -260,6 +356,19 @@ describe('#bch-use-case', () => {
assert.equal(err.message, 'hex required when calling broadcast')
}
})
it('should throw an error if selected provider is not defined', async () => {
try {
uut.ipfs.ipfsCoordAdapter.state.selectedServiceProvider = null
const hex = 'abc123'
await uut.broadcast(hex)
assert.fail('Unexpected code path')
} catch (err) {
// console.log(err)
assert.equal(err.message, 'No BCH Wallet Service provider available yet.')
}
})
})
describe('#getTransactions', () => {
@@ -290,6 +399,19 @@ describe('#bch-use-case', () => {
assert.equal(err.message, 'address required when calling getTransactions()')
}
})
it('should throw an error if selected provider is not defined', async () => {
try {
uut.ipfs.ipfsCoordAdapter.state.selectedServiceProvider = null
const addr = 'abc123'
await uut.getTransactions(addr)
assert.fail('Unexpected code path')
} catch (err) {
// console.log(err)
assert.equal(err.message, 'No BCH Wallet Service provider available yet.')
}
})
})
describe('#getTransaction', () => {
@@ -320,6 +442,19 @@ describe('#bch-use-case', () => {
assert.equal(err.message, 'txids required when calling getTransaction()')
}
})
it('should throw an error if selected provider is not defined', async () => {
try {
uut.ipfs.ipfsCoordAdapter.state.selectedServiceProvider = null
const txid = 'abc123'
await uut.getTransaction(txid)
assert.fail('Unexpected code path')
} catch (err) {
// console.log(err)
assert.equal(err.message, 'No BCH Wallet Service provider available yet.')
}
})
})
describe('#getPubKey', () => {
@@ -350,6 +485,19 @@ describe('#bch-use-case', () => {
assert.equal(err.message, 'address required when calling getPubKey()')
}
})
it('should throw an error if selected provider is not defined', async () => {
try {
uut.ipfs.ipfsCoordAdapter.state.selectedServiceProvider = null
const addr = 'abc123'
await uut.getPubKey(addr)
assert.fail('Unexpected code path')
} catch (err) {
// console.log(err)
assert.equal(err.message, 'No BCH Wallet Service provider available yet.')
}
})
})
describe('#utxoIsValid', () => {
@@ -380,6 +528,18 @@ describe('#bch-use-case', () => {
assert.equal(err.message, 'utxo required when calling utxoIsValid()')
}
})
it('should throw an error if selected provider is not defined', async () => {
try {
uut.ipfs.ipfsCoordAdapter.state.selectedServiceProvider = null
const utxo = { a: 'b' }
await uut.utxoIsValid(utxo)
assert.fail('Unexpected code path')
} catch (err) {
// console.log(err)
assert.equal(err.message, 'No BCH Wallet Service provider available yet.')
}
})
})
describe('#getTokenData', () => {
@@ -410,6 +570,19 @@ describe('#bch-use-case', () => {
assert.equal(err.message, 'tokenId required when calling getTokenData()')
}
})
it('should throw an error if selected provider is not defined', async () => {
try {
uut.ipfs.ipfsCoordAdapter.state.selectedServiceProvider = null
const tokenId = 'blah'
await uut.getTokenData(tokenId)
assert.fail('Unexpected code path')
} catch (err) {
// console.log(err)
assert.equal(err.message, 'No BCH Wallet Service provider available yet.')
}
})
})
describe('#getTokenData2', () => {
@@ -440,5 +613,18 @@ describe('#bch-use-case', () => {
assert.equal(err.message, 'tokenId required when calling getTokenData2()')
}
})
it('should throw an error if selected provider is not defined', async () => {
try {
uut.ipfs.ipfsCoordAdapter.state.selectedServiceProvider = null
const tokenId = 'blah'
await uut.getTokenData2(tokenId)
assert.fail('Unexpected code path')
} catch (err) {
// console.log(err)
assert.equal(err.message, 'No BCH Wallet Service provider available yet.')
}
})
})
})
+191 -29
View File
@@ -151,7 +151,21 @@ describe('#IPFS', () => {
}
})
})
describe('#subscribeToChat', () => {
it('should subscribe to the chat channel', async () => {
// Mock dependencies
uut.ipfsCoord = {
adapters: {
pubsub: {
subscribeToPubsubChannel: async () => {
}
}
}
}
await uut.subscribeToChat()
})
})
describe('#pollForBchServices', () => {
it('should find and select the wallet service', () => {
uut.ipfsCoord = {
@@ -169,46 +183,194 @@ describe('#IPFS', () => {
'QmWkjYRRTaxVEuGK8ip2X3trVyJShFs6U9g1h9x6fK5mZ2'
)
})
it('should use preferredProvider if set', async () => {
// Mock dependencies
uut.ipfsCoord = {
thisNode: {
peerList: mockData.peers,
peerData: mockData.peerData
}
}
// Define already selected service provider.
uut.state.serviceProviders = [mockData.peers[2]]
uut.state.selectedServiceProvider = mockData.peers[2]
// Define preferred provider.
uut.config.preferredProvider = mockData.peers[0]
sandbox.stub(uut.semver, 'gt').returns(true)
it('should catch and report errors', () => {
uut.pollForBchServices()
await uut.pollForBchServices()
assert.equal(uut.state.selectedServiceProvider, uut.config.preferredProvider)
})
it('should log unknown errors', async () => {
// Mock dependencies
uut.ipfsCoord = {
thisNode: {
peerList: mockData.peers,
peerData: mockData.peerData
}
}
sandbox.stub(uut.semver, 'gt').throws(new Error('test error'))
await uut.pollForBchServices()
assert.equal(uut.state.selectedServiceProvider, '')
assert.isOk(true, 'Not throwing an error is a success.')
})
it('should handle ipfsCoord errors', async () => {
uut.ipfsCoord = {
}
await uut.pollForBchServices()
assert.equal(uut.state.selectedServiceProvider, '')
assert.isOk(true, 'Not throwing an error is a success.')
})
})
// describe('#peerInputHandler', () => {
// it('should emit an event trigger', () => {
// const data = 'some data'
//
// uut.peerInputHandler(data)
//
// assert.isOk(true, 'Not throwing an error is a success')
// })
//
// it('should catch and report errors', () => {
// // Force an error
// sandbox.stub(uut.eventEmitter, 'emit').throws(new Error('test error'))
//
// uut.peerInputHandler()
//
// assert.isOk(true, 'Not throwing an error is a success.')
// })
// })
describe('#subscribeToChat', () => {
it('should subscribe to the chat channel', async () => {
describe('#pollForP2wdbServices', () => {
it('should poll for P2WDB services', async () => {
// Mock dependencies
uut.ipfsCoord = {
adapters: {
pubsub: {
subscribeToPubsubChannel: async () => {
}
}
thisNode: {
peerList: mockData.peers,
peerData: mockData.peerData
}
}
sandbox.stub(uut.semver, 'gt').returns(true)
await uut.pollForP2wdbServices()
assert.equal(uut.state.selectedP2wdbProvider, mockData.peers[2])
})
it('should use preferredP2wdbProvider if set', async () => {
// Mock dependencies
uut.ipfsCoord = {
thisNode: {
peerList: mockData.peers,
peerData: mockData.peerData
}
}
await uut.subscribeToChat()
uut.config.preferredP2wdbProvider = mockData.peers[2]
sandbox.stub(uut.semver, 'gt').returns(true)
await uut.pollForP2wdbServices()
assert.equal(uut.state.selectedP2wdbProvider, uut.config.preferredP2wdbProvider)
})
it('should handle a peer that does not match the protocol', async () => {
// Mock dependencies
uut.ipfsCoord = {
thisNode: {
peerList: mockData.peers,
peerData: mockData.peerData
}
}
sandbox.stub(uut.semver, 'gt').returns(false)
await uut.pollForP2wdbServices()
assert.equal(uut.state.selectedP2wdbProvider, '')
})
it('should log unknown errors', async () => {
// Mock dependencies
uut.ipfsCoord = {
thisNode: {
peerList: mockData.peers,
peerData: mockData.peerData
}
}
sandbox.stub(uut.semver, 'gt').throws(new Error('test error'))
await uut.pollForP2wdbServices()
assert.equal(uut.state.selectedP2wdbProvider, '')
assert.isOk(true, 'Not throwing an error is a success.')
})
it('should handle ipfsCoord errors', async () => {
uut.ipfsCoord = {
}
await uut.pollForP2wdbServices()
assert.equal(uut.state.selectedP2wdbProvider, '')
assert.isOk(true, 'Not throwing an error is a success.')
})
})
describe('#pollForIpfsFileServices', () => {
it('should poll for IPFS File services', async () => {
// Mock dependencies
uut.ipfsCoord = {
thisNode: {
peerList: mockData.peers,
peerData: mockData.peerData
}
}
sandbox.stub(uut.semver, 'gt').returns(true)
await uut.pollForIpfsFileServices()
assert.equal(uut.state.selectedIpfsFileProvider, mockData.peers[3])
})
it('should use preferredIpfsFileProvider if set', async () => {
// Mock dependencies
uut.ipfsCoord = {
thisNode: {
peerList: mockData.peers,
peerData: mockData.peerData
}
}
// Define already selected service provider.
uut.state.ipfsFileProviders = [mockData.peers[0]]
uut.state.selectedIpfsFileProvider = mockData.peers[0]
uut.config.preferredIpfsFileProvider = mockData.peers[3]
sandbox.stub(uut.semver, 'satisfies').returns(true)
await uut.pollForIpfsFileServices()
assert.equal(uut.state.selectedIpfsFileProvider, uut.config.preferredIpfsFileProvider)
})
it('should handle a peer that does not match the protocol', async () => {
// Mock dependencies
uut.ipfsCoord = {
thisNode: {
peerList: mockData.peers,
peerData: mockData.peerData
}
}
sandbox.stub(uut.semver, 'satisfies').returns(false)
await uut.pollForIpfsFileServices()
assert.equal(uut.state.selectedIpfsFileProvider, '')
})
it('should log unknown errors', async () => {
// Mock dependencies
uut.ipfsCoord = {
thisNode: {
peerList: mockData.peers,
peerData: mockData.peerData
}
}
sandbox.stub(uut.semver, 'satisfies').throws(new Error('test error'))
await uut.pollForIpfsFileServices()
assert.equal(uut.state.selectedIpfsFileProvider, '')
assert.isOk(true, 'Not throwing an error is a success.')
})
it('should handle ipfsCoord errors', async () => {
uut.ipfsCoord = {
}
await uut.pollForIpfsFileServices()
assert.equal(uut.state.selectedIpfsFileProvider, '')
assert.isOk(true, 'Not throwing an error is a success.')
})
})
})
@@ -0,0 +1,243 @@
/*
Unit tests for the IPFS Files Adapter.
*/
import { assert } from 'chai'
import sinon from 'sinon'
import cloneDeep from 'lodash.clonedeep'
import adapters from '../mocks/adapters/index.js'
import IPFSFilesAdapter from '../../../src/adapters/ipfs-files/index.js'
// import IPFSMock from '../mocks/ipfs-mock.js'
import mockDataLib from '../mocks/adapters/ipfs-coord-mocks.js'
import mockDataRPC from '../mocks/use-cases/rest-api-mocks.js'
describe('#IPFS-Files', () => {
let uut
let sandbox
let mockData
beforeEach(() => {
const ipfs = adapters.ipfs
uut = new IPFSFilesAdapter({ ipfs })
sandbox = sinon.createSandbox()
mockData = cloneDeep(mockDataLib)
})
afterEach(() => {
sandbox.restore()
clearInterval(uut.pollBchServiceInterval)
clearInterval(uut.pollP2wdbServiceInterval)
})
describe('#constructor', () => {
it('should throw an error if ipfs instance is not included', () => {
try {
uut = new IPFSFilesAdapter()
assert.fail('Unexpected code path')
} catch (err) {
assert.include(
err.message,
'An instance of IPFS must be passed when instantiating the IPFS Files Adapter library.'
)
}
})
})
describe('#rpcHandler', () => {
it('should add RPC data to queue', () => {
const data = {
payload: {
id: '123'
}
}
uut.rpcHandler(data)
})
it('should skip errors', async () => {
try {
sandbox.stub(uut.rpcDataQueue, 'push').throws(new Error('test error'))
await uut.rpcHandler()
} catch (err) {
assert.fail('Unexpected code path')
}
})
})
describe('#getStatus', () => {
it('should get the status from ipfs-coord', async () => {
uut.ipfs.ipfsCoordAdapter.state.ipfsFileProviders = mockData.peers
uut.ipfs.ipfsCoordAdapter.ipfsCoord.thisNode = { peerData: mockData.peerData }
await uut.getStatus()
})
it('should handle error', async () => {
try {
uut.ipfs.ipfsCoordAdapter.ipfsCoord.thisNode = null
await uut.getStatus()
assert.fail('Unexpected code path')
} catch (err) {
assert.include(err.message, 'Cannot read')
}
})
})
describe('#selectProvider', () => {
it('should select provider', async () => {
await uut.selectProvider('providerId to select')
assert.equal(uut.ipfs.ipfsCoordAdapter.config.selectedIpfsFileProvider, 'providerId to select')
})
it('should handle error', async () => {
try {
await uut.selectProvider()
assert.fail('Unexpected code path')
} catch (err) {
assert.include(err.message, 'providerId must be a string!')
}
})
})
describe('#getFileMetadata', () => {
it('should get file metadata', async () => {
uut.ipfs.ipfsCoordAdapter.state.selectedIpfsFileProvider = mockData.peers[0]
sandbox.stub(uut, 'waitForRPCResponse').resolves({
success: true
})
const result = await uut.getFileMetadata({ cid: 'cid to get' })
assert.equal(result.success, true)
})
it('should handle error if no cid is provided', async () => {
try {
await uut.getFileMetadata()
assert.fail('Unexpected code path')
} catch (err) {
assert.include(err.message, 'getFileMetadata() cid input hash must be a string.')
}
})
it('should handle error if no provider is selected', async () => {
try {
uut.ipfs.ipfsCoordAdapter.state.selectedIpfsFileProvider = null
await uut.getFileMetadata({ cid: 'cid to get' })
assert.fail('Unexpected code path')
} catch (err) {
assert.include(err.message, 'No IPFS File Pin Service provider available yet.')
}
})
})
describe('#getPins', () => {
it('should get pins', async () => {
uut.ipfs.ipfsCoordAdapter.state.selectedIpfsFileProvider = mockData.peers[0]
sandbox.stub(uut, 'waitForRPCResponse').resolves({
success: true
})
const result = await uut.getPins()
assert.equal(result.success, true)
})
it('should handle error if no provider is selected', async () => {
try {
uut.ipfs.ipfsCoordAdapter.state.selectedIpfsFileProvider = null
await uut.getPins()
assert.fail('Unexpected code path')
} catch (err) {
assert.include(err.message, 'No IPFS File Pin Service provider available yet.')
}
})
})
describe('#waitForRPCResponse', () => {
it('should resolve when data is received', async () => {
// Mock dependencies
uut.ipfs.ipfsCoordAdapter.wallet = {
bchjs: {
Util: {
sleep: () => {}
}
}
}
// Mock data.
const rpcId = '123'
uut.rpcDataQueue.push(mockDataRPC.rpcData)
const result = await uut.waitForRPCResponse(rpcId)
// console.log('result: ', result)
assert.property(result, 'success')
assert.equal(result.success, true)
assert.property(result, 'balances')
assert.isArray(result.balances)
})
it('should return false if data is not found', async () => {
// Mock dependencies
uut.ipfs.ipfsCoordAdapter.wallet = {
bchjs: {
Util: {
sleep: () => {}
}
}
}
// Mock data.
const rpcId = '123'
// Fill the queue with false values.
for (let i = 0; i < 10; i++) {
uut.rpcDataQueue.push({ payload: { id: '1234567890' } })
}
sandbox.stub(uut.ipfs.ipfsCoordAdapter.bchjs.Util, 'sleep').resolves()
const result = await uut.waitForRPCResponse(rpcId)
// console.log('result: ', result)
assert.property(result, 'success')
assert.isFalse(result.success)
})
it('should catch and throw an error', async () => {
try {
uut.ipfs.ipfsCoordAdapter.wallet = {
bchjs: {
Util: {
sleep: () => {}
}
}
}
await uut.waitForRPCResponse()
assert.fail('Unexpected code path')
} catch (err) {
// console.log('error: ', err)
assert.include(err.message, 'rpcId can not be false or undefined')
}
})
})
describe('#pinClaim', () => {
it('should process pinClaim', async () => {
uut.ipfs.ipfsCoordAdapter.state.selectedIpfsFileProvider = mockData.peers[0]
sandbox.stub(uut, 'waitForRPCResponse').resolves({
success: true
})
const result = await uut.pinClaim({
proofOfBurnTxid: 'proofOfBurnTxid to pin',
cid: 'cid to pin',
claimTxid: 'claimTxid to pin',
filename: 'filename to pin',
address: 'address to pin'
})
assert.equal(result.success, true)
})
it('should handle error if no provider is selected', async () => {
try {
uut.ipfs.ipfsCoordAdapter.state.selectedIpfsFileProvider = null
await uut.pinClaim({
proofOfBurnTxid: 'proofOfBurnTxid to pin',
cid: 'cid to pin',
claimTxid: 'claimTxid to pin',
filename: 'filename to pin',
address: 'address to pin'
})
assert.fail('Unexpected code path')
} catch (err) {
assert.include(err.message, 'No IPFS File Provider Service is available yet. Try again in a few seconds.')
}
})
})
})
+28 -133
View File
@@ -5,26 +5,17 @@
// Global npm libraries
import { assert } from 'chai'
import sinon from 'sinon'
import cloneDeep from 'lodash.clonedeep'
import { peerIdFromString } from '@libp2p/peer-id'
// Local libraries
import IPFSLib from '../../../src/adapters/ipfs/ipfs.js'
// import create from '../mocks/ipfs-mock.js'
import config from '../../../config/index.js'
import createHeliaLib from '../mocks/helia-mock.js'
// config.isProduction = true;
describe('#IPFS-adapter', () => {
let uut
let sandbox
let ipfs
beforeEach(() => {
uut = new IPFSLib()
ipfs = cloneDeep(createHeliaLib)
sandbox = sinon.createSandbox()
})
@@ -39,26 +30,37 @@ describe('#IPFS-adapter', () => {
assert.isFunction(_uut.start)
assert.isFunction(_uut.stop)
})
it('should instantiate dev IPFS Lib in production mode.', async () => {
config.isProduction = true
const _uut = new IPFSLib()
assert.exists(_uut)
assert.isFunction(_uut.start)
assert.isFunction(_uut.stop)
config.isProduction = false
})
})
describe('#start', () => {
it('should return a promise that resolves into an instance of IPFS.', async () => {
// Mock dependencies.
sandbox.stub(uut, 'createNode').resolves(ipfs)
sandbox.stub(uut, 'publicIp').resolves('192.168.2.4')
sandbox.stub(uut, 'multiaddr').returns('/ip4/fake-multiaddr')
// Mock the heliaNode that gets created inside start()
const mockIpfs = {
libp2p: {
getMultiaddrs: () => [],
peerId: 'fake-id'
}
}
// Override the start method to mock the heliaNode creation
uut.start = async function () {
this.heliaNode = {
start: async () => mockIpfs,
id: 'fake-id',
multiaddrs: ['/ip4/fake-multiaddr']
}
const ipfs = await this.heliaNode.start()
this.id = this.heliaNode.id
this.multiaddrs = this.heliaNode.multiaddrs
this.isReady = true
this.ipfs = ipfs
return this.ipfs
}
const result = await uut.start()
// console.log('result: ', result)
// Assert properties of the instance are set.
assert.equal(uut.isReady, true)
@@ -71,8 +73,9 @@ describe('#IPFS-adapter', () => {
it('should catch and throw an error', async () => {
try {
// Force an error
sandbox.stub(uut, 'createNode').rejects(new Error('test error'))
// Force an error by making getSeed throw, which will cause
// CreateHeliaNode to fail during start.
sandbox.stub(uut, 'getSeed').rejects(new Error('test error'))
await uut.start()
assert.fail('Unexpected code path.')
@@ -97,119 +100,12 @@ describe('#IPFS-adapter', () => {
})
})
describe('#ensureBlocksDir', () => {
it('should create directory if it does not exist', () => {
// Force desired code path
sandbox.stub(uut.fs, 'existsSync').returns(false)
sandbox.stub(uut.fs, 'mkdirSync').returns(true)
const result = uut.ensureBlocksDir()
assert.equal(result, true)
})
it('should report and throw errors', () => {
// Force an error
sandbox.stub(uut.fs, 'existsSync').throws(new Error('test error'))
try {
uut.ensureBlocksDir()
assert.fail('Unexpected code path')
} catch (err) {
assert.include(err.message, 'test error')
}
})
})
describe('#createNode', () => {
it('should report and throw errors', async () => {
// Force an error
sandbox.stub(uut, 'createLibp2p').rejects(new Error('test error'))
try {
await uut.createNode()
assert.fail('Unexpected code path')
} catch (err) {
assert.include(err.message, 'test error')
}
})
it('should create an IPFS node from Helia', async () => {
uut.config.isCircuitRelay = false
const result = await uut.createNode()
// console.log('result: ', result)
// Assert the returned IPFS node has expected properties
assert.property(result, 'libp2p')
assert.property(result, 'blockstore')
// Stop the IPFS node
await result.stop()
})
it('should create a Circuit Relay if configured', async () => {
uut.config.isCircuitRelay = true
const result = await uut.createNode()
// Assert the returned IPFS node has expected properties
assert.property(result, 'libp2p')
assert.property(result, 'blockstore')
// Stop the IPFS node
await result.stop()
})
it('should create a new private key on first run', async () => {
uut.config.isCircuitRelay = false
// Mock dependencies and force desired code path.
let beenCalled = false
sandbox.stub(uut, 'getKeychain').resolves({
exportPeerId: async () => {
if (!beenCalled) {
beenCalled = true
throw new Error('test error')
}
return peerIdFromString('12D3KooWSXF1PnEfiA8bCG8SJduCvzdwHtvhVPK4WC6zzDoto2XP')
},
createKey: async () => {}
})
sandbox.stub(uut, 'createLibp2p').resolves()
sandbox.stub(uut, 'createHelia').resolves({})
const result = await uut.createNode()
// console.log('result: ', result)
assert.property(result, 'fs')
})
it('should not use circuit relay when CONNECT_PREF is set to direct', async () => {
process.env.CONNECT_PREF = 'direct'
uut.config.isCircuitRelay = false
const result = await uut.createNode()
// console.log('result: ', result)
delete process.env.CONNECT_PREF
// Assert the returned IPFS node has expected properties
assert.property(result, 'libp2p')
assert.property(result, 'blockstore')
// Stop the IPFS node
await result.stop()
})
})
describe('#getSeed', () => {
it('should read the seed from the JSON file', async () => {
// Mock dependencies and force desired code path
sandbox.stub(uut.jsonFiles, 'readJSON').resolves('12345678')
const result = await uut.getSeed()
// console.log('result: ', result)
assert.isString(result)
})
@@ -220,7 +116,6 @@ describe('#IPFS-adapter', () => {
sandbox.stub(uut.jsonFiles, 'writeJSON').resolves()
const result = await uut.getSeed()
// console.log('result: ', result)
assert.isString(result)
})
+223
View File
@@ -0,0 +1,223 @@
/*
Unit tests for P2WDB Adapter.
*/
import { assert } from 'chai'
import sinon from 'sinon'
import cloneDeep from 'lodash.clonedeep'
import adapters from '../mocks/adapters/index.js'
import P2WDBAdapter from '../../../src/adapters/p2wdb/index.js'
// import IPFSMock from '../mocks/ipfs-mock.js'
import mockDataLib from '../mocks/adapters/ipfs-coord-mocks.js'
import mockDataRPC from '../mocks/use-cases/rest-api-mocks.js'
describe('#P2WDB Adapter', () => {
let uut
let sandbox
let mockData
beforeEach(() => {
const ipfs = adapters.ipfs
uut = new P2WDBAdapter({ ipfs })
sandbox = sinon.createSandbox()
mockData = cloneDeep(mockDataLib)
})
afterEach(() => {
sandbox.restore()
clearInterval(uut.pollBchServiceInterval)
clearInterval(uut.pollP2wdbServiceInterval)
})
describe('#constructor', () => {
it('should throw an error if ipfs instance is not included', () => {
try {
uut = new P2WDBAdapter()
assert.fail('Unexpected code path')
} catch (err) {
assert.include(
err.message,
'An instance of IPFS must be passed when instantiating the P2WDB Adapter library.'
)
}
})
})
describe('#rpcHandler', () => {
it('should add RPC data to queue', () => {
const data = {
payload: {
id: '123'
}
}
uut.rpcHandler(data)
})
it('should skip errors', async () => {
try {
sandbox.stub(uut.rpcDataQueue, 'push').throws(new Error('test error'))
await uut.rpcHandler()
} catch (err) {
assert.fail('Unexpected code path')
}
})
})
describe('#getStatus', () => {
it('should get the status from ipfs-coord', async () => {
uut.ipfs.ipfsCoordAdapter.state.p2wdbProviders = mockData.peers
uut.ipfs.ipfsCoordAdapter.ipfsCoord.thisNode = { peerData: mockData.peerData }
await uut.getStatus()
})
it('should handle error', async () => {
try {
uut.ipfs.ipfsCoordAdapter.ipfsCoord.thisNode = null
await uut.getStatus()
assert.fail('Unexpected code path')
} catch (err) {
assert.include(err.message, 'Cannot read')
}
})
})
describe('#selectProvider', () => {
it('should select provider', async () => {
await uut.selectProvider('providerId to select')
assert.equal(uut.ipfs.ipfsCoordAdapter.config.preferredP2wdbProvider, 'providerId to select')
})
it('should handle error', async () => {
try {
await uut.selectProvider()
assert.fail('Unexpected code path')
} catch (err) {
assert.include(err.message, 'providerId must be a string!')
}
})
})
describe('#waitForRPCResponse', () => {
it('should resolve when data is received', async () => {
// Mock dependencies
uut.ipfs.ipfsCoordAdapter.wallet = {
bchjs: {
Util: {
sleep: () => {}
}
}
}
// Mock data.
const rpcId = '123'
uut.rpcDataQueue.push(mockDataRPC.rpcData)
const result = await uut.waitForRPCResponse(rpcId)
// console.log('result: ', result)
assert.property(result, 'success')
assert.equal(result.success, true)
assert.property(result, 'balances')
assert.isArray(result.balances)
})
it('should return false if data is not found', async () => {
// Mock dependencies
uut.ipfs.ipfsCoordAdapter.wallet = {
bchjs: {
Util: {
sleep: () => {}
}
}
}
// Mock data.
const rpcId = '123'
// Fill the queue with false values.
for (let i = 0; i < 10; i++) {
uut.rpcDataQueue.push({ payload: { id: '1234567890' } })
}
sandbox.stub(uut.ipfs.ipfsCoordAdapter.bchjs.Util, 'sleep').resolves()
const result = await uut.waitForRPCResponse(rpcId)
// console.log('result: ', result)
assert.property(result, 'success')
assert.isFalse(result.success)
})
it('should catch and throw an error', async () => {
try {
uut.ipfs.ipfsCoordAdapter.wallet = {
bchjs: {
Util: {
sleep: () => {}
}
}
}
await uut.waitForRPCResponse()
assert.fail('Unexpected code path')
} catch (err) {
// console.log('error: ', err)
assert.include(err.message, 'rpcId can not be false or undefined')
}
})
})
describe('#writeEntry', () => {
it('should handle error if no provider is selected', async () => {
try {
uut.ipfs.ipfsCoordAdapter.state.selectedP2wdbProvider = null
await uut.writeEntry({
txid: 'txid to write',
signature: 'signature to write',
message: 'message to write',
data: 'data to write'
})
assert.fail('Unexpected code path')
} catch (err) {
assert.include(err.message, 'No P2WDB Service provider available yet.')
}
})
it('should process writeEntry', async () => {
uut.ipfs.ipfsCoordAdapter.state.selectedP2wdbProvider = mockData.peers[0]
sandbox.stub(uut, 'waitForRPCResponse').resolves({
success: true
})
const result = await uut.writeEntry({
txid: 'txid to write',
signature: 'signature to write',
message: 'message to write',
data: 'data to write'
})
assert.equal(result.success, true)
})
})
describe('#getEntryByHash', () => {
it('should handle error if hash is not provided', async () => {
try {
await uut.getEntryByHash()
assert.fail('Unexpected code path')
} catch (err) {
assert.include(err.message, 'getEntry() input hash must be a string.')
}
})
it('should handle error if no provider is selected', async () => {
try {
uut.ipfs.ipfsCoordAdapter.state.selectedP2wdbProvider = null
await uut.getEntryByHash('hash to get')
assert.fail('Unexpected code path')
} catch (err) {
assert.include(err.message, 'No P2WDB Service provider available yet.')
}
})
it('should process getEntryByHash', async () => {
uut.ipfs.ipfsCoordAdapter.state.selectedP2wdbProvider = mockData.peers[0]
sandbox.stub(uut, 'waitForRPCResponse').resolves({
success: true
})
const result = await uut.getEntryByHash('hash to get')
assert.equal(result.success, true)
})
})
})
@@ -41,5 +41,17 @@ describe('#passport.js', () => {
assert.include(err.message, 'cant auth user')
}
})
it('should authenticate user', async () => {
const ctx = {}
const errMock = null
const userMock = {
_id: '123',
email: 'test@test.com'
}
sandbox.stub(uut.passport, 'authenticate').yields(errMock, userMock)
const user = await uut.authUser(ctx)
assert.equal(user._id, userMock._id)
assert.equal(user.email, userMock.email)
})
})
})
+7
View File
@@ -54,6 +54,13 @@ describe('#User-Adapter', () => {
assert.notEqual(testuser.password, 'password')
})
it('should ignore password encrption if password property is not provided', async () => {
const lastPassword = testuser.password
await testuser.save()
// console.log('testuser: ', testuser)
assert.equal(testuser.password, lastPassword)
})
})
describe('#validatePassword', () => {
+21
View File
@@ -38,4 +38,25 @@ describe('#Controllers', () => {
await uut.attachControllers(app)
})
})
describe('#attachRESTControllers', () => {
it('should attach the controllers', async () => {
const app = {
use: () => {}
}
await uut.attachRESTControllers(app)
})
})
describe('#initAdapters', () => {
it('should attach the controllers', async () => {
sandbox.stub(uut.adapters, 'start').resolves({})
await uut.initAdapters()
})
})
describe('#initUseCases', () => {
it('should attach the controllers', async () => {
sandbox.stub(uut.useCases, 'start').resolves({})
await uut.initUseCases()
})
})
})
@@ -162,6 +162,40 @@ describe('#JSON RPC', () => {
assert.equal(obj.result.method, 'auth')
assert.equal(obj.id, id)
})
it('should route to bch handler on success payload', async () => {
const id = uid()
const json = jsonrpc.success(id, { method: 'bch' })
const str = JSON.stringify(json)
// Mock the controller.
sandbox.stub(uut.adapters.bch, 'rpcHandler').resolves('true')
const result = await uut.router(str, 'peerA')
// console.log(result)
const obj = JSON.parse(result.retStr)
// console.log('obj: ', obj)
assert.equal(obj.result.value, 'true')
assert.equal(obj.id, id)
})
it('should route to bch handler on success payload', async () => {
const id = uid()
const json = jsonrpc.success(id, { method: 'file-pin' })
const str = JSON.stringify(json)
// Mock the controller.
sandbox.stub(uut.adapters.ipfsFiles, 'rpcHandler').resolves('true')
const result = await uut.router(str, 'peerA')
// console.log(result)
const obj = JSON.parse(result.retStr)
// console.log('obj: ', obj)
assert.equal(obj.result.value, 'true')
assert.equal(obj.id, id)
})
it('should route to about handler', async () => {
const id = uid()
@@ -188,6 +222,31 @@ describe('#JSON RPC', () => {
assert.equal(obj.result.method, 'about')
assert.equal(obj.id, id)
})
it('should route to bch handler', async () => {
const id = uid()
const userCall = jsonrpc.request(id, 'bch', { endpoint: 'getAll' })
const jsonStr = JSON.stringify(userCall, null, 2)
// Mock the controller.
sandbox.stub(uut.adapters.bch, 'rpcHandler').resolves('true')
// Force ipfs-coord communication.
uut.ipfsCoord.ipfs = {
orbitdb: {
sendToDb: () => {}
}
}
const result = await uut.router(jsonStr, 'peerA')
// console.log(result)
const obj = JSON.parse(result.retStr)
// console.log('obj: ', obj)
assert.equal(obj.result.value, 'true')
assert.equal(obj.result.method, 'bch')
assert.equal(obj.id, id)
})
it('should exit quietly for duplicate RPC message', async () => {
const id = uid()
@@ -79,11 +79,22 @@ describe('#Auth-REST-Router', () => {
await uut.authUser(ctx)
})
it('should catch and throw an error', async () => {
it('should catch and throw passport error', async () => {
try {
// Force an error
sandbox.stub(uut.passport, 'authUser').rejects('test error')
await uut.authUser(ctx)
} catch (err) {
// console.log('err: ', err)
assert.include(err.message, 'Unauthorized')
}
})
it('should handle error if user is not found!', async () => {
try {
// Force an error
sandbox.stub(uut.passport, 'authUser').resolves(null)
await uut.authUser(ctx)
} catch (err) {
// console.log('err: ', err)
@@ -62,6 +62,18 @@ describe('#BCH-REST-Controller', () => {
})
describe('#handleError', () => {
it('should pass an error message', () => {
try {
const err = {
status: 422,
message: 'Unprocessable Entity'
}
uut.handleError(ctx, err)
} catch (err) {
assert.include(err.message, 'Unprocessable Entity')
}
})
it('should still throw error if there is no message', () => {
try {
const err = {
@@ -467,4 +479,67 @@ describe('#BCH-REST-Controller', () => {
assert.equal(ctx.status, 200)
})
})
describe('#getService', () => {
it('should return 422 status on arbitrary error', async () => {
try {
sandbox.stub(uut.adapters.ipfs.ipfsCoordAdapter, 'state').value(null)
ctx.request.body = {
service: 'blah'
}
await uut.getService(ctx)
assert.fail('Unexpected result')
} catch (err) {
console.log('err: ', err)
assert.equal(err.status, 422)
assert.include(err.message, 'Cannot read')
}
})
it('should return 200 status on success', async () => {
sandbox.stub(uut.adapters.ipfs.ipfsCoordAdapter, 'state').value({ selectedServiceProvider: 'selected service' })
ctx.request.body = {
service: 'selected service'
}
await uut.getService(ctx)
// Assert the expected HTTP response
assert.equal(ctx.status, 200)
})
})
describe('#postProvider', () => {
it('should return 422 status on arbitrary error', async () => {
try {
uut.config.freezeProvider = true
ctx.request.body = {
providerId: 'selected provider id'
}
await uut.postProvider(ctx)
assert.fail('Unexpected result')
} catch (err) {
console.log('err: ', err)
assert.equal(err.status, 422)
assert.include(err.message, 'Consumer has preferredProvider set in environment variable. Refusing to switch providers.')
}
})
it('should return 200 status on success', async () => {
uut.config.freezeProvider = false
ctx.request.body = {
providerId: 'selected provider id'
}
sandbox.stub(uut.adapters.bch, 'selectProvider').resolves(true)
await uut.postProvider(ctx)
// Assert the expected HTTP response
assert.equal(ctx.status, 200)
})
})
})
@@ -18,7 +18,7 @@ let sandbox
// const mockContext = require('../../../../unit/mocks/ctx-mock').context
describe('#Users-REST-Router', () => {
describe('#BCH-REST-Router', () => {
// const testUser = {}
beforeEach(() => {
@@ -75,4 +75,108 @@ describe('#Users-REST-Router', () => {
}
})
})
describe('#getStatus', () => {
it('should route to controller', async () => {
const spy = sandbox.stub(uut.bchRESTController, 'getStatus').resolves(true)
await uut.getStatus()
assert.isTrue(spy.calledOnce)
})
})
describe('#postProvider', () => {
it('should route to controller', async () => {
const spy = sandbox.stub(uut.bchRESTController, 'postProvider').resolves(true)
await uut.postProvider()
assert.isTrue(spy.calledOnce)
})
})
describe('#postBalance', () => {
it('should route to controller', async () => {
const spy = sandbox.stub(uut.bchRESTController, 'balance').resolves(true)
await uut.postBalance()
assert.isTrue(spy.calledOnce)
})
})
describe('#postUtxos', () => {
it('should route to controller', async () => {
const spy = sandbox.stub(uut.bchRESTController, 'utxos').resolves(true)
await uut.postUtxos()
assert.isTrue(spy.calledOnce)
})
})
describe('#postUtxosBulk', () => {
it('should route to controller', async () => {
const spy = sandbox.stub(uut.bchRESTController, 'utxosBulk').resolves(true)
await uut.postUtxosBulk()
assert.isTrue(spy.calledOnce)
})
})
describe('#postBroadcast', () => {
it('should route to controller', async () => {
const spy = sandbox.stub(uut.bchRESTController, 'broadcast').resolves(true)
await uut.postBroadcast()
assert.isTrue(spy.calledOnce)
})
})
describe('#postTxHistory', () => {
it('should route to controller', async () => {
const spy = sandbox.stub(uut.bchRESTController, 'txHistory').resolves(true)
await uut.postTxHistory()
assert.isTrue(spy.calledOnce)
})
})
describe('#postTxData', () => {
it('should route to controller', async () => {
const spy = sandbox.stub(uut.bchRESTController, 'txData').resolves(true)
await uut.postTxData()
assert.isTrue(spy.calledOnce)
})
})
describe('#postPubKey', () => {
it('should route to controller', async () => {
const spy = sandbox.stub(uut.bchRESTController, 'pubKey').resolves(true)
await uut.postPubKey()
assert.isTrue(spy.calledOnce)
})
})
describe('#utxoIsValid', () => {
it('should route to controller', async () => {
const spy = sandbox.stub(uut.bchRESTController, 'utxoIsValid').resolves(true)
await uut.utxoIsValid()
assert.isTrue(spy.calledOnce)
})
})
describe('#getTokenData', () => {
it('should route to controller', async () => {
const spy = sandbox.stub(uut.bchRESTController, 'getTokenData').resolves(true)
await uut.getTokenData()
assert.isTrue(spy.calledOnce)
})
})
describe('#getTokenData2', () => {
it('should route to controller', async () => {
const spy = sandbox.stub(uut.bchRESTController, 'getTokenData2').resolves(true)
await uut.getTokenData2()
assert.isTrue(spy.calledOnce)
})
})
describe('#getService', () => {
it('should route to controller', async () => {
const spy = sandbox.stub(uut.bchRESTController, 'getService').resolves(true)
await uut.getService()
assert.isTrue(spy.calledOnce)
})
})
})
@@ -248,4 +248,247 @@ describe('#IPFS REST API', () => {
assert.property(ctx.body, 'thisNode')
})
})
describe('#viewFile', () => {
it('should return 422 status on biz logic error', async () => {
try {
// Force an error
ctx.params = {
cid: 'bafybeib2rphswe7clvclverw7xi7nejkpkh4mdfcurdmcgw7wcup7za6wy'
}
sandbox.stub(uut.useCases.ipfs, 'downloadCid').throws(new Error('test error'))
uut.adapters.ipfs.ipfsCoordAdapter = {}
ctx.request.body = {}
await uut.viewFile(ctx)
assert.fail('Unexpected result')
} catch (err) {
// console.log('err: ', err)
assert.equal(err.status, 422)
assert.include(err.message, 'test error')
}
})
it('should return 200 status on success', async () => {
ctx.params = {
cid: 'bafybeib2rphswe7clvclverw7xi7nejkpkh4mdfcurdmcgw7wcup7za6wy'
}
// Mock dependencies
sandbox.stub(uut.useCases.ipfs, 'downloadCid').resolves({ success: true, readStream: 'readStream' })
uut.adapters.ipfs.ipfsCoordAdapter = {
ipfsCoord: {
thisNode: {}
}
}
ctx.request.body = {}
await uut.viewFile(ctx)
assert.exists(ctx.body)
})
})
describe('#downloadFile', () => {
it('should return 422 status on biz logic error', async () => {
try {
ctx.params = {
cid: 'bafybeib2rphswe7clvclverw7xi7nejkpkh4mdfcurdmcgw7wcup7za6wy'
}
// Force an error
sandbox.stub(uut.useCases.ipfs, 'downloadCid').throws(new Error('test error'))
uut.adapters.ipfs.ipfsCoordAdapter = {}
ctx.request.body = {}
await uut.downloadFile(ctx)
assert.fail('Unexpected result')
} catch (err) {
// console.log('err: ', err)
assert.equal(err.status, 422)
assert.include(err.message, 'test error')
}
})
it('should return 200 status on success', async () => {
ctx.params = {
cid: 'bafybeib2rphswe7clvclverw7xi7nejkpkh4mdfcurdmcgw7wcup7za6wy'
}
// Mock dependencies
sandbox.stub(uut.useCases.ipfs, 'downloadCid').resolves({ success: true, readStream: 'readStream' })
uut.adapters.ipfs.ipfsCoordAdapter = {
ipfsCoord: {
thisNode: {}
}
}
ctx.request.body = {}
await uut.downloadFile(ctx)
assert.exists(ctx.body)
})
})
describe('#getService', () => {
it('should return 422 status on biz logic error', async () => {
try {
// Force an error
uut.adapters.ipfs.ipfsCoordAdapter = {}
ctx.request.body = {}
await uut.getService(ctx)
assert.fail('Unexpected result')
} catch (err) {
// console.log('err: ', err)
assert.equal(err.status, 422)
assert.include(err.message, 'Cannot read properties of undefined')
}
})
it('should return 200 status on success', async () => {
// Mock dependencies
uut.adapters.ipfs.ipfsCoordAdapter = {
state: {
selectedIpfsFileProvider: 'provider'
}
}
ctx.request.body = {}
await uut.getService(ctx)
assert.isTrue(ctx.body.success)
})
})
describe('#getFileInfo', () => {
it('should return 422 status on biz logic error', async () => {
try {
ctx.params = {
cid: 'bafybeib2rphswe7clvclverw7xi7nejkpkh4mdfcurdmcgw7wcup7za6wy'
}
sandbox.stub(uut.adapters.ipfsFiles, 'getFileMetadata').throws(new Error('test error'))
ctx.request.body = {}
await uut.getFileInfo(ctx)
assert.fail('Unexpected result')
} catch (err) {
// console.log('err: ', err)
assert.equal(err.status, 422)
assert.include(err.message, 'test error')
}
})
it('should return 200 status on success', async () => {
ctx.params = {
cid: 'bafybeib2rphswe7clvclverw7xi7nejkpkh4mdfcurdmcgw7wcup7za6wy'
}
// Mock dependencies
sandbox.stub(uut.adapters.ipfsFiles, 'getFileMetadata').resolves({ success: true, filename: 'filename' })
ctx.request.body = {}
await uut.getFileInfo(ctx)
assert.isTrue(ctx.body.success)
})
})
describe('#getPins', () => {
it('should return 422 status on biz logic error', async () => {
try {
sandbox.stub(uut.adapters.ipfsFiles, 'getPins').throws(new Error('test error'))
ctx.request.body = {}
ctx.params = { page: 1 }
await uut.getPins(ctx)
assert.fail('Unexpected result')
} catch (err) {
assert.equal(err.status, 422)
assert.include(err.message, 'test error')
}
})
it('should return 200 status on success', async () => {
// Mock dependencies
sandbox.stub(uut.adapters.ipfsFiles, 'getPins').resolves({ success: true })
ctx.request.body = {}
ctx.params = { page: 1 }
await uut.getPins(ctx)
assert.isTrue(ctx.body.success)
})
})
describe('#cid2json', () => {
it('should return 422 status on biz logic error', async () => {
try {
ctx.params = {
cid: 'bafybeib2rphswe7clvclverw7xi7nejkpkh4mdfcurdmcgw7wcup7za6wy'
}
sandbox.stub(uut.useCases.ipfs, 'cid2json').throws(new Error('test error'))
ctx.request.body = {}
await uut.cid2json(ctx)
assert.fail('Unexpected result')
} catch (err) {
assert.equal(err.status, 422)
assert.include(err.message, 'test error')
}
})
it('should return 200 status on success', async () => {
ctx.params = {
cid: 'bafybeib2rphswe7clvclverw7xi7nejkpkh4mdfcurdmcgw7wcup7za6wy'
}
// Mock dependencies
sandbox.stub(uut.useCases.ipfs, 'cid2json').resolves({ success: true })
ctx.request.body = {}
await uut.cid2json(ctx)
assert.isTrue(ctx.body.success)
})
})
describe('#pinClaim', () => {
it('should return 422 status on biz logic error', async () => {
try {
sandbox.stub(uut.useCases.ipfs, 'pinClaim').throws(new Error('test error'))
ctx.request.body = {}
await uut.pinClaim(ctx)
assert.fail('Unexpected result')
} catch (err) {
assert.equal(err.status, 422)
assert.include(err.message, 'test error')
}
})
it('should return 200 status on success', async () => {
// Mock dependencies
sandbox.stub(uut.useCases.ipfs, 'pinClaim').resolves({ success: true })
ctx.request.body = {}
await uut.pinClaim(ctx)
assert.isTrue(ctx.body.success)
})
})
})
@@ -0,0 +1,66 @@
/*
Unit tests for the REST API middleware that handle response errors.
*/
// Public npm libraries
import { assert } from 'chai'
import sinon from 'sinon'
// Local libraries
import errorMiddleware from '../../../../../src/controllers/rest-api/middleware/error.js'
import { context as mockContext } from '../../../../unit/mocks/ctx-mock.js'
describe('#Validators', () => {
let ctx
let sandbox
beforeEach(() => {
// Mock the context object.
ctx = mockContext()
sandbox = sinon.createSandbox()
})
afterEach(() => sandbox.restore())
describe('#errorMiddleware', () => {
it('should run next function', async () => {
// Spy on next
const next = sinon.spy(() => { })
errorMiddleware()(ctx, next)
assert.isTrue(next.calledOnce)
})
it('should handle unknown status error', async () => {
try {
const next = async () => {
const e = new Error('test error')
e.status = null
throw e
}
await errorMiddleware()(ctx, next)
assert.fail('Unexpected code path')
} catch (error) {
assert.equal(ctx.status, 500)
assert.equal(ctx.body, 'test error')
}
})
it('should handle known status error', async () => {
try {
const next = async () => {
const e = new Error('test error')
e.status = 422
throw e
}
await errorMiddleware()(ctx, next)
assert.fail('Unexpected code path')
} catch (error) {
assert.equal(ctx.status, 422)
assert.equal(ctx.body, 'test error')
}
})
})
})
@@ -0,0 +1,223 @@
/*
Unit tests for the REST API handler for the /p2wdb endpoints.
*/
// Public npm libraries
import { assert } from 'chai'
import sinon from 'sinon'
// Local support libraries
import adapters from '../../../mocks/adapters/index.js'
import UseCasesMock from '../../../mocks/use-cases/index.js'
import P2wdbController from '../../../../../src/controllers/rest-api/p2wdb/controller.js'
import { context } from '../../../mocks/ctx-mock.js'
// import blah '../../../../unit/mocks/ctx-mock.js'
// console.log('blah: ', blah)
let uut
let sandbox
let ctx
describe('#P2WDB-REST-Controller', () => {
// const testUser = {}
beforeEach(() => {
const useCases = new UseCasesMock()
uut = new P2wdbController({ adapters, useCases })
sandbox = sinon.createSandbox()
// Mock the context object.
ctx = context()
})
afterEach(() => sandbox.restore())
describe('#constructor', () => {
it('should throw an error if adapters are not passed in', () => {
try {
uut = new P2wdbController()
assert.fail('Unexpected code path')
} catch (err) {
assert.include(
err.message,
'Instance of Adapters library required when instantiating /p2wdb REST Controller.'
)
}
})
it('should throw an error if useCases are not passed in', () => {
try {
uut = new P2wdbController({ adapters })
assert.fail('Unexpected code path')
} catch (err) {
assert.include(
err.message,
'Instance of Use Cases library required when instantiating /p2wdb REST Controller.'
)
}
})
})
describe('#handleError', () => {
it('should pass an error message', () => {
try {
const err = {
status: 422,
message: 'Unprocessable Entity'
}
uut.handleError(ctx, err)
} catch (err) {
assert.include(err.message, 'Unprocessable Entity')
}
})
it('should still throw error if there is no message', () => {
try {
const err = {
status: 404
}
uut.handleError(ctx, err)
} catch (err) {
assert.include(err.message, 'Not Found')
}
})
})
describe('#getStatus', () => {
it('should return 422 status on arbitrary biz logic error', async () => {
try {
// Force an error
sandbox
.stub(uut.adapters.p2wdb, 'getStatus')
.rejects(new Error('test error'))
await uut.getStatus(ctx)
assert.fail('Unexpected result')
} catch (err) {
console.log('err: ', err)
assert.equal(err.status, 422)
assert.include(err.message, 'test error')
}
})
it('should return 200 status on success', async () => {
sandbox.stub(uut.adapters.p2wdb, 'getStatus').resolves('p2wdb')
await uut.getStatus(ctx)
// Assert the expected HTTP response
assert.equal(ctx.status, 200)
// Assert that expected properties exist in the returned data.
assert.equal(ctx.response.body.status, 'p2wdb')
})
})
describe('#postProvider', () => {
it('should return 422 status on arbitrary error', async () => {
try {
// Force an error
sandbox
.stub(uut.adapters.p2wdb, 'selectProvider')
.rejects(new Error('test error'))
ctx.request.body = {
addresses: 'blah'
}
await uut.postProvider(ctx)
assert.fail('Unexpected result')
} catch (err) {
console.log('err: ', err)
assert.equal(err.status, 422)
assert.include(err.message, 'test error')
}
})
it('should return 200 status on success', async () => {
sandbox.stub(uut.adapters.p2wdb, 'selectProvider').resolves({ status: 200 })
ctx.request.body = {
addresses: 'blah'
}
await uut.postProvider(ctx)
// Assert the expected HTTP response
assert.equal(ctx.status, 200)
})
})
describe('#getEntryByHash', () => {
it('should return 422 status on arbitrary error', async () => {
try {
// Force an error
sandbox
.stub(uut.adapters.p2wdb, 'getEntryByHash')
.rejects(new Error('test error'))
ctx.request.body = {
hash: 'blah'
}
await uut.getEntryByHash(ctx)
} catch (err) {
assert.equal(err.status, 422)
assert.include(err.message, 'test error')
}
})
it('should return 200 status on success', async () => {
sandbox.stub(uut.adapters.p2wdb, 'getEntryByHash').resolves({ status: 200 })
ctx.request.body = {
hash: 'blah'
}
await uut.getEntryByHash(ctx)
assert.equal(ctx.status, 200)
})
})
describe('#writeEntry', () => {
it('should return 422 status on arbitrary error', async () => {
try {
// Force an error
sandbox
.stub(uut.adapters.p2wdb, 'writeEntry')
.rejects(new Error('test error'))
ctx.request.body = {
txid: 'blah',
signature: 'blah',
message: 'blah',
data: 'blah'
}
await uut.writeEntry(ctx)
} catch (err) {
assert.equal(err.status, 422)
assert.include(err.message, 'test error')
}
})
it('should return 200 status on success', async () => {
sandbox.stub(uut.adapters.p2wdb, 'writeEntry').resolves({ status: 200 })
ctx.request.body = {
txid: 'blah',
signature: 'blah',
message: 'blah',
data: 'blah'
}
await uut.writeEntry(ctx)
assert.equal(ctx.status, 200)
})
})
})
@@ -0,0 +1,110 @@
/*
Unit tests for the REST API handler for the /users endpoints.
*/
// Public npm libraries
import { assert } from 'chai'
import sinon from 'sinon'
// Local support libraries
import adapters from '../../../mocks/adapters/index.js'
import UseCasesMock from '../../../mocks/use-cases/index.js'
// const app = require('../../../mocks/app-mock')
import P2wdbRouter from '../../../../../src/controllers/rest-api/p2wdb/index.js'
let uut
let sandbox
// let ctx
// const mockContext = require('../../../../unit/mocks/ctx-mock').context
describe('#P2WDB-REST-Router', () => {
// const testUser = {}
beforeEach(() => {
const useCases = new UseCasesMock()
uut = new P2wdbRouter({ adapters, useCases })
sandbox = sinon.createSandbox()
// Mock the context object.
// ctx = mockContext()
})
afterEach(() => sandbox.restore())
describe('#constructor', () => {
it('should throw an error if adapters are not passed in', () => {
try {
uut = new P2wdbRouter()
assert.fail('Unexpected code path')
} catch (err) {
assert.include(
err.message,
'Instance of Adapters library required when instantiating P2WDB REST Controller.'
)
}
})
it('should throw an error if useCases are not passed in', () => {
try {
uut = new P2wdbRouter({ adapters })
assert.fail('Unexpected code path')
} catch (err) {
assert.include(
err.message,
'Instance of Use Cases library required when instantiating P2WDB REST Controller.'
)
}
})
})
describe('#attach', () => {
it('should throw an error if app is not passed in.', () => {
try {
uut.attach()
assert.fail('Unexpected code path')
} catch (err) {
assert.include(
err.message,
'Must pass app object when attaching REST API controllers.'
)
}
})
})
describe('#getStatus', () => {
it('should route to controller', async () => {
const spy = sandbox.stub(uut.p2wdbRESTController, 'getStatus').resolves(true)
await uut.getStatus()
assert.isTrue(spy.calledOnce)
})
})
describe('#postProvider', () => {
it('should route to controller', async () => {
const spy = sandbox.stub(uut.p2wdbRESTController, 'postProvider').resolves(true)
await uut.postProvider()
assert.isTrue(spy.calledOnce)
})
})
describe('#entryFromHash', () => {
it('should route to controller', async () => {
const spy = sandbox.stub(uut.p2wdbRESTController, 'getEntryByHash').resolves(true)
await uut.entryFromHash()
assert.isTrue(spy.calledOnce)
})
})
describe('#write', () => {
it('should route to controller', async () => {
const spy = sandbox.stub(uut.p2wdbRESTController, 'writeEntry').resolves(true)
await uut.write()
assert.isTrue(spy.calledOnce)
})
})
})
@@ -0,0 +1,157 @@
/*
Unit tests for the REST API handler for the /price endpoints.
*/
// Public npm libraries
import { assert } from 'chai'
import sinon from 'sinon'
// Local support libraries
import adapters from '../../../mocks/adapters/index.js'
import UseCasesMock from '../../../mocks/use-cases/index.js'
import PriceController from '../../../../../src/controllers/rest-api/price/controller.js'
import { context } from '../../../mocks/ctx-mock.js'
// import blah '../../../../unit/mocks/ctx-mock.js'
// console.log('blah: ', blah)
let uut
let sandbox
let ctx
describe('#PRICE-REST-Controller', () => {
// const testUser = {}
beforeEach(() => {
const useCases = new UseCasesMock()
uut = new PriceController({ adapters, useCases })
sandbox = sinon.createSandbox()
// Mock the context object.
ctx = context()
})
afterEach(() => sandbox.restore())
describe('#constructor', () => {
it('should throw an error if adapters are not passed in', () => {
try {
uut = new PriceController()
assert.fail('Unexpected code path')
} catch (err) {
assert.include(
err.message,
'Instance of Adapters library required when instantiating /price REST Controller.'
)
}
})
it('should throw an error if useCases are not passed in', () => {
try {
uut = new PriceController({ adapters })
assert.fail('Unexpected code path')
} catch (err) {
assert.include(
err.message,
'Instance of Use Cases library required when instantiating /price REST Controller.'
)
}
})
})
describe('#handleError', () => {
it('should pass an error message', () => {
try {
const err = {
status: 422,
message: 'Unprocessable Entity'
}
uut.handleError(ctx, err)
} catch (err) {
assert.include(err.message, 'Unprocessable Entity')
}
})
it('should still throw error if there is no message', () => {
try {
const err = {
status: 404
}
uut.handleError(ctx, err)
} catch (err) {
assert.include(err.message, 'Not Found')
}
})
})
describe('#getUSD', () => {
it('should return 422 status on arbitrary biz logic error', async () => {
try {
// Force an error
sandbox
.stub(uut.axios, 'request')
.rejects(new Error('test error'))
await uut.getUSD(ctx)
assert.fail('Unexpected result')
} catch (err) {
console.log('err: ', err)
assert.equal(err.status, 422)
assert.include(err.message, 'test error')
}
})
it('should return 200 status on success', async () => {
sandbox.stub(uut.axios, 'request').resolves({ data: { data: { rates: { USD: 1.00 } } } })
await uut.getUSD(ctx)
// Assert the expected HTTP response
assert.equal(ctx.status, 200)
// Assert that expected properties exist in the returned data.
assert.equal(ctx.response.body.usd, 1.00)
})
})
describe('#getXecPrice', () => {
it('should return 422 status on arbitrary biz logic error', async () => {
try {
sandbox.stub(uut.axios, 'request').rejects(new Error('test error'))
await uut.getXecPrice(ctx)
assert.fail('Unexpected result')
} catch (err) {
assert.equal(err.status, 422)
assert.include(err.message, 'test error')
}
})
it('should return 200 status on success', async () => {
sandbox.stub(uut.axios, 'request').resolves({ data: { data: { ticker: { last: 1.00 } } } })
await uut.getXecPrice(ctx)
assert.equal(ctx.status, 200)
assert.equal(ctx.response.body.usd, 1.00)
})
})
describe('#getPsffppWritePrice', () => {
it('should return 200 status on success', async () => {
sandbox.stub(uut.useCases.ipfs, 'getWritePrice').resolves(1.00)
await uut.getPsffppWritePrice(ctx)
assert.equal(ctx.status, 200)
assert.equal(ctx.response.body.psfPrice, 1.00)
})
it('should return 422 status on arbitrary biz logic error', async () => {
try {
sandbox.stub(uut.useCases.ipfs, 'getWritePrice').rejects(new Error('test error'))
await uut.getPsffppWritePrice(ctx)
assert.fail('Unexpected result')
} catch (err) {
assert.equal(err.status, 422)
assert.include(err.message, 'test error')
}
})
})
})
@@ -0,0 +1,102 @@
/*
Unit tests for the REST API handler for the /users endpoints.
*/
// Public npm libraries
import { assert } from 'chai'
import sinon from 'sinon'
// Local support libraries
import adapters from '../../../mocks/adapters/index.js'
import UseCasesMock from '../../../mocks/use-cases/index.js'
// const app = require('../../../mocks/app-mock')
import PriceRouter from '../../../../../src/controllers/rest-api/price/index.js'
let uut
let sandbox
// let ctx
// const mockContext = require('../../../../unit/mocks/ctx-mock').context
describe('#Price-REST-Router', () => {
// const testUser = {}
beforeEach(() => {
const useCases = new UseCasesMock()
uut = new PriceRouter({ adapters, useCases })
sandbox = sinon.createSandbox()
// Mock the context object.
// ctx = mockContext()
})
afterEach(() => sandbox.restore())
describe('#constructor', () => {
it('should throw an error if adapters are not passed in', () => {
try {
uut = new PriceRouter()
assert.fail('Unexpected code path')
} catch (err) {
assert.include(
err.message,
'Instance of Adapters library required when instantiating Price REST Controller.'
)
}
})
it('should throw an error if useCases are not passed in', () => {
try {
uut = new PriceRouter({ adapters })
assert.fail('Unexpected code path')
} catch (err) {
assert.include(
err.message,
'Instance of Use Cases library required when instantiating Price REST Controller.'
)
}
})
})
describe('#attach', () => {
it('should throw an error if app is not passed in.', () => {
try {
uut.attach()
assert.fail('Unexpected code path')
} catch (err) {
assert.include(
err.message,
'Must pass app object when attaching REST API controllers.'
)
}
})
})
describe('#getPrice', () => {
it('should route to controller', async () => {
const spy = sandbox.stub(uut.priceRESTController, 'getUSD').resolves(true)
await uut.getPrice()
assert.isTrue(spy.calledOnce)
})
})
describe('#getXecPrice', () => {
it('should route to controller', async () => {
const spy = sandbox.stub(uut.priceRESTController, 'getXecPrice').resolves(true)
await uut.getXecPrice()
assert.isTrue(spy.calledOnce)
})
})
describe('#getPsffppWritePrice', () => {
it('should route to controller', async () => {
const spy = sandbox.stub(uut.priceRESTController, 'getPsffppWritePrice').resolves(true)
await uut.getPsffppWritePrice()
assert.isTrue(spy.calledOnce)
})
})
})
@@ -20,7 +20,7 @@ describe('#RESTControllers', () => {
let sandbox
// let ctx
before(async () => {})
before(async () => { })
beforeEach(() => {
const useCases = new UseCasesMock()
@@ -64,4 +64,19 @@ describe('#RESTControllers', () => {
}
})
})
describe('#attachRESTControllers', () => {
it('should attach controllers without mongo service', () => {
uut.config.noMongo = true
const app = { use: () => {} }
uut.attachRESTControllers(app)
})
it('should attach controllers with mongo service', () => {
uut.config.noMongo = false
const app = { use: () => {} }
uut.attachRESTControllers(app)
})
})
})
@@ -0,0 +1,163 @@
/*
Unit tests for the REST API handler for the /usage endpoints.
*/
// Public npm libraries
import { assert } from 'chai'
import sinon from 'sinon'
// Local support libraries
import adapters from '../../../mocks/adapters/index.js'
import UseCasesMock from '../../../mocks/use-cases/index.js'
import UsageController from '../../../../../src/controllers/rest-api/usage/controller.js'
import { context as mockContext } from '../../../mocks/ctx-mock.js'
let uut
let sandbox
let ctx
describe('#Usage-REST-Controller', () => {
// const testUser = {}
beforeEach(() => {
const useCases = new UseCasesMock()
uut = new UsageController({ adapters, useCases })
sandbox = sinon.createSandbox()
// Mock the context object.
ctx = mockContext()
})
afterEach(() => sandbox.restore())
describe('#constructor', () => {
it('should throw an error if adapters are not passed in', () => {
try {
uut = new UsageController()
assert.fail('Unexpected code path')
} catch (err) {
assert.include(
err.message,
'Instance of Adapters library required when instantiating /usage REST Controller.'
)
}
})
it('should throw an error if useCases are not passed in', () => {
try {
uut = new UsageController({ adapters })
assert.fail('Unexpected code path')
} catch (err) {
assert.include(
err.message,
'Instance of Use Cases library required when instantiating /usage REST Controller.'
)
}
})
})
describe('#Get /usage', () => {
it('should return 422 status on biz logic error', async () => {
try {
sandbox.stub(uut.useCases.usage, 'getRestSummary').throws(new Error('test error'))
await uut.getStatus(ctx)
assert.fail('Unexpected result')
} catch (err) {
// console.log(err)
assert.equal(err.status, 422)
assert.include(err.message, 'test error')
}
})
it('should return 200 status on success', async () => {
await uut.getStatus(ctx)
// Assert the expected HTTP response
assert.equal(ctx.status, 200)
// Assert that expected properties exist in the returned data.
assert.property(ctx.response.body, 'status')
})
})
describe('#Get /ips', () => {
it('should return 422 status on biz logic error', async () => {
try {
sandbox.stub(uut.useCases.usage, 'getTopIps').throws(new Error('test error'))
await uut.getTopIps(ctx)
assert.fail('Unexpected result')
} catch (err) {
// console.log(err)
assert.equal(err.status, 422)
assert.include(err.message, 'test error')
}
})
it('should return 200 status on success', async () => {
await uut.getTopIps(ctx)
// Assert the expected HTTP response
assert.equal(ctx.status, 200)
// Assert that expected properties exist in the returned data.
assert.property(ctx.response.body, 'ips')
})
})
describe('#Get /endpoints', () => {
it('should return 422 status on biz logic error', async () => {
try {
sandbox.stub(uut.useCases.usage, 'getTopEndpoints').throws(new Error('test error'))
await uut.getTopEndpoints(ctx)
assert.fail('Unexpected result')
} catch (err) {
// console.log(err)
assert.equal(err.status, 422)
assert.include(err.message, 'test error')
}
})
it('should return 200 status on success', async () => {
await uut.getTopEndpoints(ctx)
// Assert the expected HTTP response
assert.equal(ctx.status, 200)
// Assert that expected properties exist in the returned data.
assert.property(ctx.response.body, 'endpoints')
})
})
describe('#handleError', () => {
it('should pass an error message', () => {
try {
const err = {
status: 422,
message: 'Unprocessable Entity'
}
uut.handleError(ctx, err)
} catch (err) {
assert.include(err.message, 'Unprocessable Entity')
}
})
it('should still throw error if there is no message', () => {
try {
const err = {
status: 404
}
uut.handleError(ctx, err)
} catch (err) {
assert.include(err.message, 'Not Found')
}
})
})
})
@@ -0,0 +1,75 @@
/*
Unit tests for the REST API handler for the /usage endpoints.
*/
// Public npm libraries
import { assert } from 'chai'
import sinon from 'sinon'
// Local support libraries
import adapters from '../../../mocks/adapters/index.js'
import UseCasesMock from '../../../mocks/use-cases/index.js'
import UsageRouter from '../../../../../src/controllers/rest-api/usage/index.js'
let uut
let sandbox
// let ctx
// const mockContext = require('../../../../unit/mocks/ctx-mock').context
describe('#Usage-REST-Router', () => {
beforeEach(() => {
const useCases = new UseCasesMock()
uut = new UsageRouter({ adapters, useCases })
sandbox = sinon.createSandbox()
})
afterEach(() => sandbox.restore())
describe('#constructor', () => {
it('should throw an error if adapters are not passed in', () => {
try {
uut = new UsageRouter()
assert.fail('Unexpected code path')
} catch (err) {
assert.include(
err.message,
'Instance of Adapters library required when instantiating IPFS REST Controller.'
)
}
})
it('should throw an error if useCases are not passed in', () => {
try {
uut = new UsageRouter({ adapters })
assert.fail('Unexpected code path')
} catch (err) {
assert.include(
err.message,
'Instance of Use Cases library required when instantiating IPFS REST Controller.'
)
}
})
})
describe('#attach', () => {
it('should throw an error if app is not passed in.', () => {
try {
uut.attach()
assert.fail('Unexpected code path')
} catch (err) {
assert.include(
err.message,
'Must pass app object when attaching REST API controllers.'
)
}
})
})
})
@@ -153,6 +153,20 @@ describe('#Users-REST-Controller', () => {
// Assert that expected properties exist in the returned data.
assert.property(ctx.response.body, 'user')
})
it('should run next function if it exists', async () => {
// Mock dependencies
const nextSpy = sandbox.spy()
sandbox.stub(uut.useCases.user, 'getUser').resolves({ _id: '123' })
await uut.getUser(ctx, nextSpy)
// Assert the expected HTTP response
assert.equal(ctx.status, 200)
// Assert that expected properties exist in the returned data.
assert.property(ctx.response.body, 'user')
assert.isTrue(nextSpy.calledOnce)
})
it('should return other error status passed by biz logic', async () => {
try {
@@ -79,4 +79,70 @@ describe('#Users-REST-Router', () => {
}
})
})
describe('#createUser', () => {
it('should ignore admin validator when DISABLE_NEW_ACCOUNTS is not defined', async () => {
// Stub functions
const validationSpy = sandbox.stub(uut.validators, 'ensureAdmin').resolves(true)
sandbox.stub(uut.userRESTController, 'createUser').resolves(true)
// Call function
await uut.createUser()
// Assertions
assert.isTrue(validationSpy.notCalled, 'Admin validator should not be called')
})
it('should ensure admin when DISABLE_NEW_ACCOUNTS is defined', async () => {
// Set environment variable
process.env.DISABLE_NEW_ACCOUNTS = true
// Stub functions
const validationSpy = sandbox.stub(uut.validators, 'ensureAdmin').resolves(true)
sandbox.stub(uut.userRESTController, 'createUser').resolves(true)
// Call function
await uut.createUser()
// Assertions
assert.isTrue(validationSpy.calledOnce, 'Admin validator should be called')
})
})
describe('#getAll', () => {
it('should route to controller', async () => {
sandbox.stub(uut.validators, 'ensureUser').resolves(true)
const spy = sandbox.stub(uut.userRESTController, 'getUsers').resolves(true)
await uut.getAll()
assert.isTrue(spy.calledOnce)
})
})
describe('#getById', () => {
it('should route to controller', async () => {
sandbox.stub(uut.validators, 'ensureUser').resolves(true)
const spy = sandbox.stub(uut.userRESTController, 'getUser').resolves(true)
await uut.getById()
assert.isTrue(spy.calledOnce)
})
})
describe('#updateUser', () => {
it('should route to controller', async () => {
sandbox.stub(uut.validators, 'ensureTargetUserOrAdmin').resolves(true)
sandbox.stub(uut.userRESTController, 'getUser').resolves(true)
const spy = sandbox.stub(uut.userRESTController, 'updateUser').resolves(true)
await uut.updateUser()
assert.isTrue(spy.calledOnce)
})
})
describe('#deleteUser', () => {
it('should route to controller', async () => {
sandbox.stub(uut.validators, 'ensureTargetUserOrAdmin').resolves(true)
sandbox.stub(uut.userRESTController, 'getUser').resolves(true)
const spy = sandbox.stub(uut.userRESTController, 'deleteUser').resolves(true)
await uut.deleteUser()
assert.isTrue(spy.calledOnce)
})
})
})
@@ -66,15 +66,47 @@ describe('#Timer-Controllers', () => {
})
})
describe('#exampleTimerFunc', () => {
// describe('#exampleTimerFunc', () => {
// it('should kick off the Use Case', async () => {
// const result = await uut.exampleTimerFunc()
// assert.equal(result, true)
// })
// it('should return false on error', async () => {
// const result = await uut.exampleTimerFunc(true)
// assert.equal(result, false)
// })
// })
describe('#cleanUsage', () => {
it('should kick off the Use Case', async () => {
const result = await uut.exampleTimerFunc()
const result = await uut.cleanUsage()
assert.equal(result, true)
})
it('should return false on error', async () => {
const result = await uut.exampleTimerFunc(true)
sandbox.stub(uut.useCases.usage, 'cleanUsage').throws(new Error('test error'))
const result = await uut.cleanUsage()
assert.equal(result, false)
})
})
describe('#backupUsage', () => {
it('should kick off the Use Case', async () => {
const result = await uut.backupUsage()
assert.equal(result, true)
})
it('should return false on error', async () => {
sandbox.stub(uut.useCases.usage, 'clearUsage').throws(new Error('test error'))
// sandbox.stub(uut.useCases.usage, 'saveUsage').throws(new Error('test error'))
const result = await uut.backupUsage()
assert.equal(result, false)
})
+14 -5
View File
@@ -9,16 +9,25 @@ let currentEnv
describe('#config', () => {
before(() => {
// Backup the current environment setting.
currentEnv = process.env.SVC_ENV
currentEnv = process.env.CONSUMER_ENV
// Clear CONSUMER_ENV for the first test to ensure default behavior
delete process.env.CONSUMER_ENV
})
after(() => {
// Restore the environment setting before starting these tests.
process.env.SVC_ENV = currentEnv
if (currentEnv) {
process.env.CONSUMER_ENV = currentEnv
} else {
delete process.env.CONSUMER_ENV
}
})
it('Should return development environment config by default', async () => {
const importedConfig = await import('../../../config/index.js')
// Ensure CONSUMER_ENV is not set for this test
delete process.env.CONSUMER_ENV
const importedConfig = await import('../../../config/index.js?foo=bar0')
const config = importedConfig.default
// console.log('config: ', config)
@@ -29,7 +38,7 @@ describe('#config', () => {
// Hack to dynamically import a library multiple times:
// https://github.com/denoland/deno/issues/6946
process.env.SVC_ENV = 'test'
process.env.CONSUMER_ENV = 'test'
const importedConfig2 = await import('../../../config/index.js?foo=bar1')
const config = importedConfig2.default
@@ -39,7 +48,7 @@ describe('#config', () => {
})
it('Should return prod environment config', async () => {
process.env.SVC_ENV = 'prod'
process.env.CONSUMER_ENV = 'prod'
process.env.WALLET_INTERFACE = 'web2'
process.env.APISERVER = 'https://api.fullstack.cash/v5/'
+15
View File
@@ -34,6 +34,12 @@ describe('#passport', () => {
passportCallback(id, 'password', done)
})
it('should handle not found user', () => {
// Mock Users model.
sandbox.stub(User, 'findOne').resolves(null)
passportCallback(id, 'password', done)
})
it('should return if password is validated', () => {
// Mock Users model.
@@ -41,6 +47,15 @@ describe('#passport', () => {
passportCallback(id, 'password', done)
})
it('should handle error on password validation', () => {
// Mock Users model.
const userMock = {
validatePassword: () => false
}
sandbox.stub(User, 'findOne').resolves(userMock)
passportCallback(id, 'password', done)
})
it('should catch a high-level error', () => {
// Force an error
+1 -1
View File
@@ -30,7 +30,7 @@ describe('#server', () => {
sandbox.stub(uut.adminLib, 'createSystemUser').resolves(true)
sandbox.stub(uut.controllers, 'attachControllers').resolves()
uut.config.env = 'dev'
uut.config.port = 5040
const result = await uut.startServer()
// console.log('result: ', result)
+94 -4
View File
@@ -1,10 +1,26 @@
/*
Mocks for the Adapter library.
*/
class BlockstoreMock {
constructor () {
this.get = async () => {}
}
}
class FsMock {
constructor () {
this.ls = async function* () {
yield { path: 'test.txt', cid: 'QmS4ghgMgfFvqPjB4WKXHaN15ZyT4K4JYZxY5X5x5x5x5' }
}
this.cat = async function* () {
yield Buffer.from('test data')
}
}
}
class IpfsAdapter {
constructor () {
this.ipfs = {
blockstore: new BlockstoreMock(),
fs: new FsMock(),
files: {
stat: () => {}
}
@@ -14,7 +30,7 @@ class IpfsAdapter {
class IpfsCoordAdapter {
constructor () {
this.config = {}
this.ipfsCoord = {
adapters: {
ipfs: {
@@ -34,7 +50,9 @@ class IpfsCoordAdapter {
this.peerInputHandler = () => {}
this.state = {
serviceProviders: []
serviceProviders: [],
serviceProvidersByType: '',
selectedIpfsFileProvider:''
}
}
}
@@ -79,6 +97,39 @@ const localdb = {
}
},
Usage: class Usage {
static findById () {}
static find () {}
static findOne () {
return {
validatePassword: localdb.validatePassword
}
}
async save () {
return {}
}
generateToken () {
return '123'
}
toJSON () {
return {}
}
async remove () {
return true
}
async validatePassword () {
return true
}
static async deleteMany(){
return true
}
},
validatePassword: () => {
return true
}
@@ -136,7 +187,46 @@ class BchUseCaseMock {
async waitForRPCResponse () {
return {}
}
async selectProvider (){
return true
}
}
const bch = new BchUseCaseMock()
const wallet = {
bchWallet: bch
export default { ipfs, localdb, bch }
}
const ipfsFiles = {
pinClaim: () => {
return {
success: true,
message: 'Pin claimed'
}
},
getFileMetadata: () => {
return {
success: true,
message: 'Files metadata'
}
},
getPins: () => {
return {
success: true,
message: 'Pins'
}
},
rpcHandler: () => {
return {
success: true,
message: 'RPC handler'
}
}
}
const p2wdb = {
getStatus: async () => {},
selectProvider: async () => {},
getEntryByHash: async () => {},
writeEntry: async () => {}
}
export default { ipfs, localdb, bch, wallet, ipfsFiles, p2wdb }
+111 -3
View File
@@ -4,7 +4,9 @@
const peers = [
'QmWkjYRRTaxVEuGK8ip2X3trVyJShFs6U9g1h9x6fK5mZ2',
'QmQqjVVD3CS6zaJmoSdt6GwX1c1tD5T9FZas4qxLbru8xN'
'QmQqjVVD3CS6zaJmoSdt6GwX1c1tD5T9FZas4qxLbru8xN',
'QmWkjYRRTaxVEuGK8ip2X3trVyJShFs6U9g1h9x6fK5mT3',
'QmWkjYRRTaxVEuGK8ip2X3trVyJShFs6U9g1h9x6fK5mD4'
]
const peerData = [
@@ -47,7 +49,7 @@ const peerData = [
'@type': 'WebAPI',
name: 'trout-bch-wallet-service-dev',
version: '1.11.12',
protocol: 'bch-wallet',
protocol: 'bch-wallet-service',
description:
'IPFS service providing BCH blockchain access needed by a wallet.',
documentation: 'https://ipfs-bch-wallet-service.fullstack.cash/',
@@ -110,7 +112,113 @@ const peerData = [
},
updatedAt: '2021-08-24T23:13:20.411Z'
}
}
},
{
from: 'QmWkjYRRTaxVEuGK8ip2X3trVyJShFs6U9g1h9x6fK5mT3',
channel: 'psf-ipfs-coordination-001',
data: {
apiName: 'ipfs-coord-announce',
apiVersion: '1.3.2',
apiInfo:
'You should put an IPFS hash or web URL here to your documentation.',
ipfsId: 'QmWkjYRRTaxVEuGK8ip2X3trVyJShFs6U9g1h9x6fK5mZ2',
type: 'node.js',
ipfsMultiaddrs: [
'/ip4/127.0.0.1/tcp/5701/p2p/QmWkjYRRTaxVEuGK8ip2X3trVyJShFs6U9g1h9x6fK5mZ2',
'/ip4/127.0.0.1/tcp/5702/ws/p2p/QmWkjYRRTaxVEuGK8ip2X3trVyJShFs6U9g1h9x6fK5mZ2',
'/ip4/192.168.0.2/tcp/5701/p2p/QmWkjYRRTaxVEuGK8ip2X3trVyJShFs6U9g1h9x6fK5mZ2',
'/ip4/192.168.0.2/tcp/5702/ws/p2p/QmWkjYRRTaxVEuGK8ip2X3trVyJShFs6U9g1h9x6fK5mZ2'
],
orbitdb:
'/orbitdb/zdpuAntCMCcmZuFy2hq5F7kpcti3yHa34LLZHzyVqorkoSpAJ/QmWkjYRRTaxVEuGK8ip2X3trVyJShFs6U9g1h9x6fK5mZ221082417',
circuitRelays: [],
isCircuitRelay: false,
cryptoAddresses: [
{
blockchain: 'BCH',
type: 'cashAddr',
address: 'bitcoincash:qru6kq3p4tv6z2lmy0n560lyhh3z2feay5gjzggc37'
},
{
blockchain: 'BCH',
type: 'slpAddr',
address: 'simpleledger:qru6kq3p4tv6z2lmy0n560lyhh3z2feay5yffnac0q'
}
],
encryptPubKey:
'021db6e97650659653ba61dd493dc348a7429cdcbafd4fb73b08b1223bf5bd98df',
jsonLd: {
'@context': 'https://schema.org/',
'@type': 'WebAPI',
name: 'trout-bch-wallet-service-dev',
version: '1.11.12',
protocol: 'p2wdb',
description:
'IPFS service providing BCH blockchain access needed by a wallet.',
documentation: 'https://ipfs-bch-wallet-service.fullstack.cash/',
provider: {
'@type': 'Organization',
name: 'Permissionless Software Foundation',
url: 'https://PSFoundation.cash'
},
identifier: 'QmWkjYRRTaxVEuGK8ip2X3trVyJShFs6U9g1h9x6fK5mZ2'
},
updatedAt: '2021-08-24T23:13:19.306Z'
}
},
{
from: 'QmWkjYRRTaxVEuGK8ip2X3trVyJShFs6U9g1h9x6fK5mD4',
channel: 'psf-ipfs-coordination-001',
data: {
apiName: 'ipfs-coord-announce',
apiVersion: '1.3.2',
apiInfo:
'You should put an IPFS hash or web URL here to your documentation.',
ipfsId: 'QmWkjYRRTaxVEuGK8ip2X3trVyJShFs6U9g1h9x6fK5mZ2',
type: 'node.js',
ipfsMultiaddrs: [
'/ip4/127.0.0.1/tcp/5701/p2p/QmWkjYRRTaxVEuGK8ip2X3trVyJShFs6U9g1h9x6fK5mZ2',
'/ip4/127.0.0.1/tcp/5702/ws/p2p/QmWkjYRRTaxVEuGK8ip2X3trVyJShFs6U9g1h9x6fK5mZ2',
'/ip4/192.168.0.2/tcp/5701/p2p/QmWkjYRRTaxVEuGK8ip2X3trVyJShFs6U9g1h9x6fK5mZ2',
'/ip4/192.168.0.2/tcp/5702/ws/p2p/QmWkjYRRTaxVEuGK8ip2X3trVyJShFs6U9g1h9x6fK5mZ2'
],
orbitdb:
'/orbitdb/zdpuAntCMCcmZuFy2hq5F7kpcti3yHa34LLZHzyVqorkoSpAJ/QmWkjYRRTaxVEuGK8ip2X3trVyJShFs6U9g1h9x6fK5mZ221082417',
circuitRelays: [],
isCircuitRelay: false,
cryptoAddresses: [
{
blockchain: 'BCH',
type: 'cashAddr',
address: 'bitcoincash:qru6kq3p4tv6z2lmy0n560lyhh3z2feay5gjzggc37'
},
{
blockchain: 'BCH',
type: 'slpAddr',
address: 'simpleledger:qru6kq3p4tv6z2lmy0n560lyhh3z2feay5yffnac0q'
}
],
encryptPubKey:
'021db6e97650659653ba61dd493dc348a7429cdcbafd4fb73b08b1223bf5bd98df',
jsonLd: {
'@context': 'https://schema.org/',
'@type': 'WebAPI',
name: 'trout-bch-wallet-service-dev',
version: '1.11.12',
protocol: 'ipfs-file-pin-service',
description:
'IPFS service providing BCH blockchain access needed by a wallet.',
documentation: 'https://ipfs-bch-wallet-service.fullstack.cash/',
provider: {
'@type': 'Organization',
name: 'Permissionless Software Foundation',
url: 'https://PSFoundation.cash'
},
identifier: 'QmWkjYRRTaxVEuGK8ip2X3trVyJShFs6U9g1h9x6fK5mZ2'
},
updatedAt: '2021-08-24T23:13:19.306Z'
}
},
]
// module.exports = {
+1 -1
View File
@@ -17,7 +17,7 @@ class MockBchWallet {
this.walletInfoPromise = true;
this.walletInfo = mockWallet;
this.initialize = async () => {}
this.bchjs = new BCHJS();
this.bchjs = new BCHJS({ restURL: 'https://free-bch.fullstack.cash/' });
this.burnTokens = async () => {
return { success: true, txid: 'txid' };
};
+54
View File
@@ -49,6 +49,58 @@ class BchUseCaseMock {
}
}
class UsageUseCaseMock {
async cleanUsage() {
return {}
}
async getRestSummary() {
return true
}
async getTopIps(params) {
return true
}
async getTopEndpoints(existingUser, newData) {
return true
}
async clearUsage() {
return true
}
async saveUsage() {
return true
}
}
class IpfsUseCaseMock {
async downloadCid() {
return {}
}
async cid2json() {
return {}
}
async getCidMetadata() {
return {}
}
async getService() {
return {}
}
async pinClaim() {
return {}
}
async getWritePrice() {
return 1.00
}
}
class UseCasesMock {
constuctor(localConfig = {}) {
// this.user = new UserUseCaseMock(localConfig)
@@ -58,6 +110,8 @@ class UseCasesMock {
user = new UserUseCaseMock()
bch = new BchUseCaseMock()
usage = new UsageUseCaseMock()
ipfs = new IpfsUseCaseMock()
}
export default UseCasesMock;
+282
View File
@@ -0,0 +1,282 @@
/*
Unit tests for the ipfs-use-cases.js file.
*/
// Public npm libraries
import { assert } from 'chai'
import sinon from 'sinon'
// Local support libraries
// const testUtils = require('../../utils/test-utils')
// Unit under test (uut)
import UseCases from '../../../src/use-cases/ipfs-use-cases.js'
import adapters from '../mocks/adapters/index.js'
describe('#ipfs-use-cases', () => {
let uut
let sandbox
before(async () => {
// Delete all previous users in the database.
// await testUtils.deleteAllUsers()
})
beforeEach(() => {
sandbox = sinon.createSandbox()
uut = new UseCases({ adapters })
uut.adapters.ipfs.ipfsCoordAdapter = {
state: {
selectedIpfsFileProvider: 'provider'
}
}
})
afterEach(() => sandbox.restore())
describe('#constructor', () => {
it('should throw an error if adapters are not passed in', () => {
try {
uut = new UseCases()
assert.fail('Unexpected code path')
// This is here to prevent the linter from complaining.
assert.isOk(uut)
} catch (err) {
assert.include(
err.message,
'Instance of adapters must be passed in when instantiating IPFS Use Cases library.'
)
}
})
})
describe('#downloadCid', () => {
it('should download a file from IPFS', async () => {
sandbox.stub(uut, 'getCidMetadata').resolves('file.txt')
const result = await uut.downloadCid({ cid: 'bafybeib2rphswe7clvclverw7xi7nejkpkh4mdfcurdmcgw7wcup7za6wy' })
assert.property(result, 'filename')
assert.property(result, 'readStream')
})
it('should handle directory-based CIDs', async () => {
sandbox.stub(uut, 'getCidMetadata').resolves('file.txt')
sandbox.stub(uut.adapters.ipfs.ipfs.fs, 'ls').callsFake(async function * () {
yield { path: 'dir1/test.txt', cid: 'QmS4ghgMgfFvqPjB4WKXHaN15ZyT4K4JYZxY5X5x5x5x5' }
})
const result = await uut.downloadCid({ cid: 'bafybeib2rphswe7clvclverw7xi7nejkpkh4mdfcurdmcgw7wcup7za6wy' })
assert.property(result, 'filename')
assert.property(result, 'readStream')
})
it('should throw an error if cid is not provided ', async () => {
try {
await uut.downloadCid({})
} catch (error) {
assert.include(error.message, 'CID is undefined')
}
})
})
describe('#getWritePrice', () => {
it('should get price', async () => {
const result = await uut.getWritePrice()
assert.isNumber(result)
})
it('should handle undefined psffpp instance', async () => {
uut.psffpp = null
const result = await uut.getWritePrice()
assert.isNumber(result)
})
it('should re-instantiate psffpp instance', async () => {
const psffppMock = {}
uut.psffpp = psffppMock
const lastWritePriceUpdate = new Date()
lastWritePriceUpdate.setHours(lastWritePriceUpdate.getHours() - 10)
uut.lastWritePriceUpdate = lastWritePriceUpdate.getTime()
const result = await uut.getWritePrice()
assert.isNumber(result)
assert.notEqual(uut.lastWritePriceUpdate, lastWritePriceUpdate.getTime())
assert.notEqual(uut.psffpp, psffppMock)
})
it('should handle error ', async () => {
try {
uut.lastWritePriceUpdate = new Date().getTime()
uut.psffpp = {
getMcWritePrice: () => {
throw new Error('test error')
}
}
await uut.getWritePrice()
assert.fail('Unexpected code path')
} catch (error) {
assert.include(error.message, 'test error')
}
})
})
describe('#getCidMetadata', () => {
it('should get cid metadata', async () => {
sandbox.stub(uut.adapters.ipfsFiles, 'getFileMetadata').resolves({ success: true, fileMetadata: { dataPinned: true, filename: 'test.json' } })
const result = await uut.getCidMetadata({ cid: 'bafybeib2rphswe7clvclverw7xi7nejkpkh4mdfcurdmcgw7wcup7za6wy' })
assert.isString(result)
assert.equal(result, 'test.json')
})
it('should handle error ', async () => {
try {
sandbox.stub(uut.adapters.ipfsFiles, 'getFileMetadata').throws(new Error('test error'))
await uut.getCidMetadata({ cid: 'bafybeib2rphswe7clvclverw7xi7nejkpkh4mdfcurdmcgw7wcup7za6wy' })
} catch (error) {
assert.include(error.message, 'test error')
}
})
it('should handle non-success response from getFileMetadata', async () => {
try {
sandbox.stub(uut.adapters.ipfsFiles, 'getFileMetadata').resolves({ success: false, message: 'test error' })
await uut.getCidMetadata({ cid: 'bafybeib2rphswe7clvclverw7xi7nejkpkh4mdfcurdmcgw7wcup7za6wy' })
} catch (error) {
assert.include(error.message, 'test error')
}
})
})
describe('#cid2json', () => {
it('should get cid2json', async () => {
uut.adapters.ipfs.ipfsCoordAdapter = {
state: {
selectedIpfsFileProvider: 'provider'
}
}
sandbox.stub(uut.adapters.ipfsFiles, 'getFileMetadata').resolves({ fileMetadata: { dataPinned: true, filename: 'test.json' } })
sandbox.stub(uut.adapters.ipfs.ipfs.fs, 'ls').callsFake(function * () {
yield { name: 'test.json' }
})
sandbox.stub(JSON, 'parse').returns(true)
const result = await uut.cid2json({ cid: 'bafybeib2rphswe7clvclverw7xi7nejkpkh4mdfcurdmcgw7wcup7za6wy' })
assert.isObject(result)
assert.property(result, 'success')
assert.property(result, 'json')
})
it('should throw error if selectedIpfsFileProvider is not defined', async () => {
try {
uut.adapters.ipfs.ipfsCoordAdapter = {
state: {
selectedIpfsFileProvider: null
}
}
await uut.cid2json({ cid: 'bafybeib2rphswe7clvclverw7xi7nejkpkh4mdfcurdmcgw7wcup7za6wy' })
assert.fail('Unexpected code path')
} catch (error) {
assert.include(error.message, 'No IPFS File Provider Service is available yet. Try again in a few seconds')
}
})
it('should throw error if file metadata cant be fetched', async () => {
try {
sandbox.stub(uut.adapters.ipfsFiles, 'getFileMetadata').resolves({ success: false })
await uut.cid2json({ cid: 'bafybeib2rphswe7clvclverw7xi7nejkpkh4mdfcurdmcgw7wcup7za6wy' })
assert.fail('Unexpected code path')
} catch (error) {
assert.include(error.message, 'Could not communicate with instance of ipfs-file-pin-service')
}
})
it('should throw error if cid is not pinned', async () => {
try {
sandbox.stub(uut.adapters.ipfsFiles, 'getFileMetadata').resolves({ success: true, fileMetadata: { dataPinned: false } })
await uut.cid2json({ cid: 'bafybeib2rphswe7clvclverw7xi7nejkpkh4mdfcurdmcgw7wcup7za6wy' })
assert.fail('Unexpected code path')
} catch (error) {
assert.include(error.message, 'has not been pinned by ipfs-file-pin-service instance')
}
})
it('should throw error if filename has no .json extension', async () => {
try {
sandbox.stub(uut.adapters.ipfsFiles, 'getFileMetadata').resolves({ success: true, fileMetadata: { dataPinned: true, filename: 'test' } })
await uut.cid2json({ cid: 'bafybeib2rphswe7clvclverw7xi7nejkpkh4mdfcurdmcgw7wcup7za6wy' })
assert.fail('Unexpected code path')
} catch (error) {
assert.include(error.message, 'does not resolve to a JSON file')
}
})
it('should handle error if file cant be retrieved', async () => {
try {
sandbox.stub(uut.adapters.ipfsFiles, 'getFileMetadata').resolves({ fileMetadata: { dataPinned: true, filename: 'test.json' } })
sandbox.stub(uut.adapters.ipfs.ipfs.fs, 'cat').throws(new Error('test error'))
sandbox.stub(uut.adapters.ipfs.ipfs.fs, 'ls').callsFake(function * () {
yield { name: 'test.json' }
})
await uut.cid2json({ cid: 'bafybeib2rphswe7clvclverw7xi7nejkpkh4mdfcurdmcgw7wcup7za6wy' })
assert.fail('Unexpected code path')
} catch (error) {
assert.include(error.message, 'test error')
}
})
it('should handle error if cid is a directory', async () => {
try {
sandbox.stub(uut.adapters.ipfsFiles, 'getFileMetadata').resolves({ fileMetadata: { dataPinned: true, filename: 'test.json' } })
sandbox.stub(uut.adapters.ipfs.ipfs.fs, 'cat')
.onCall(0).throws(new Error('test error'))
.onCall(1).callsFake(function * () {
yield { name: 'test.json' }
})
sandbox.stub(uut.adapters.ipfs.ipfs.fs, 'ls').callsFake(function * () {
yield { name: 'test/directory/' }
})
await uut.cid2json({ cid: 'bafybeib2rphswe7clvclverw7xi7nejkpkh4mdfcurdmcgw7wcup7za6wy' })
} catch (error) {
assert.include(error.message, 'file does not resolve to a valid JSON file')
}
})
it('should get content from cid/name.json format', async () => {
sandbox.stub(uut.adapters.ipfsFiles, 'getFileMetadata').resolves({ fileMetadata: { dataPinned: true, filename: 'test.json' } })
sandbox.stub(uut.adapters.ipfs.ipfs.fs, 'cat')
.onCall(0).throws(new Error('test error'))
.onCall(1).callsFake(function * () {
yield Buffer.from('{"test": "test"}')
})
sandbox.stub(uut.adapters.ipfs.ipfs.fs, 'ls').callsFake(function * () {
yield { name: 'test/directory/test.json' }
})
const result = await uut.cid2json({ cid: 'bafybeib2rphswe7clvclverw7xi7nejkpkh4mdfcurdmcgw7wcup7za6wy' })
assert.isObject(result)
assert.property(result, 'success')
assert.property(result, 'json')
})
it('should handle if content cant be parsed', async () => {
try {
sandbox.stub(uut.adapters.ipfsFiles, 'getFileMetadata').resolves({ fileMetadata: { dataPinned: true, filename: 'test.json' } })
sandbox.stub(uut.adapters.ipfs.ipfs.fs, 'ls').callsFake(function * () {
yield { name: 'test.json' }
})
sandbox.stub(JSON, 'parse').throws(new Error())
await uut.cid2json({ cid: 'bafybeib2rphswe7clvclverw7xi7nejkpkh4mdfcurdmcgw7wcup7za6wy' })
} catch (error) {
assert.include(error.message, 'not resolve to a valid JSON object')
}
})
})
describe('#pinClaim', () => {
it('should process pinClaim', async () => {
const result = await uut.pinClaim()
assert.isObject(result)
assert.property(result, 'success')
assert.property(result, 'message')
})
it('should handle error ', async () => {
try {
sandbox.stub(uut.adapters.ipfsFiles, 'pinClaim').throws(new Error('test error'))
await uut.pinClaim()
} catch (error) {
assert.include(error.message, 'test error')
}
})
})
})
+322
View File
@@ -0,0 +1,322 @@
/*
Unit tests for the use-cases/usage-use-cases.js business logic library.
*/
// Public npm libraries
import { assert } from 'chai'
import sinon from 'sinon'
// Local support libraries
import adapters from '../mocks/adapters/index.js'
// Mock
import { context as mockContext } from '../mocks/ctx-mock.js'
// Unit under test (uut)
import { UsageUseCases, restCalls, usageMiddleware } from '../../../src/use-cases/usage-use-cases.js'
describe('#usage-use-case', () => {
let uut
let sandbox
let ctx
before(async () => {
})
beforeEach(() => {
sandbox = sinon.createSandbox()
uut = new UsageUseCases({ adapters })
console.log('restCalls: ', restCalls)
// Set as empty array
restCalls.splice(0, restCalls.length)
ctx = mockContext()
})
afterEach(() => sandbox.restore())
describe('#constructor', () => {
it('should throw an error if adapters are not passed in', () => {
try {
uut = new UsageUseCases()
assert.fail('Unexpected code path')
} catch (err) {
assert.include(
err.message,
'Instance of adapters must be passed in when instantiating Usage Use Cases library.'
)
}
})
})
describe('#cleanUsage', () => {
it('should delete older data than 24 hours', () => {
const now = new Date() // Mock date
// set older mock data
restCalls.push({
timestamp: now.getTime() - (60000 * 60 * 48), // 48 hours ago
ip: '127.0.0.1'
})
// Set recently mock data
restCalls.push({
timestamp: now.getTime(),
ip: 'localhost'
})
const result = uut.cleanUsage()
assert.isArray(result)
assert.equal(result.length, 1)
assert.equal(result[0].ip, 'localhost')
})
it('should handle error', () => {
try {
// Force an error
sandbox.stub(restCalls, 'filter').throws(new Error('uut error'))
uut.cleanUsage()
assert.fail('Unexpected code path')
} catch (error) {
assert.equal(error.message, 'uut error')
}
})
})
describe('#getRestSummary', () => {
it('should get the number of rest calls', () => {
// Set mock data
restCalls.push({
ip: 'localhost'
})
const result = uut.getRestSummary()
assert.isNumber(result)
assert.equal(result, 1)
})
it('should handle error', () => {
try {
// Force an error
sandbox.stub(console, 'log').throws(new Error('uut error'))
uut.getRestSummary()
assert.fail('Unexpected code path')
} catch (error) {
assert.equal(error.message, 'uut error')
}
})
})
describe('#getTopIps', () => {
it('should get top IPs', () => {
// Set mock data
restCalls.push({
ip: 'localhost'
})
// Set mock data
restCalls.push({
ip: 'localhost'
})
const result = uut.getTopIps()
assert.isArray(result)
assert.property(result[0], 'ip')
assert.property(result[0], 'cnt')
assert.equal(result[0].ip, 'localhost')
assert.equal(result[0].cnt, '2')
})
it('should return a maximum of 20 values', () => {
// Fill Array with 21 values
for (let i = 0; i < 21; i++) {
restCalls.push({
ip: `localhost-${i}`
})
}
const result = uut.getTopIps()
assert.isArray(result)
assert.property(result[0], 'ip')
assert.property(result[0], 'cnt')
assert.equal(result.length, 20)
})
it('should handle error', () => {
try {
// Set mock data
restCalls.push(null)
uut.getTopIps()
assert.fail('Unexpected code path')
} catch (error) {
assert.include(error.message, 'Cannot read properties')
}
})
})
describe('#getTopEndpoints', () => {
it('should get top Endpoints', () => {
// Set mock data
restCalls.push({
ip: 'localhost',
url: '/api/v1/users',
method: 'GET'
})
// Set mock data
restCalls.push({
ip: 'localhost',
url: '/api/v1/users',
method: 'GET'
})
const result = uut.getTopEndpoints()
assert.isArray(result)
assert.property(result[0], 'endpoint')
assert.property(result[0], 'cnt')
assert.equal(result[0].endpoint, 'GET /api/v1/users')
assert.equal(result[0].cnt, '2')
})
it('should return a maximum of 20 values', () => {
// Fill Array with 21 values
for (let i = 0; i < 21; i++) {
restCalls.push({
ip: 'localhost',
url: `/api/v1/users-${i}`,
method: 'GET'
})
}
const result = uut.getTopEndpoints()
assert.isArray(result)
assert.property(result[0], 'endpoint')
assert.property(result[0], 'cnt')
assert.equal(result.length, 20)
})
it('should handle error', () => {
try {
// Set mock data
restCalls.push(null)
uut.getTopEndpoints()
assert.fail('Unexpected code path')
} catch (error) {
assert.include(error.message, 'Cannot read properties')
}
})
})
describe('#usageMiddleware', () => {
it('should update restCalls state', async () => {
// Spy on next
const next = sinon.spy(() => { })
await usageMiddleware()(ctx, next)
assert.equal(restCalls.length, 1)
assert.isTrue(next.called)
})
it('should handle error', async () => {
try {
const next = () => { throw new Error('uut error') }
await usageMiddleware()(ctx, next)
assert.fail('Unexpected code path')
} catch (error) {
assert.equal(error.message, 'uut error')
assert.equal(ctx.status, 500)
assert.equal(restCalls.length, 0)
}
})
})
describe('#clearUsage', () => {
it('should clear the usage database data', async () => {
const res = await uut.clearUsage()
assert.isTrue(res)
})
it('should handle error', async () => {
try {
sandbox.stub(uut.UsageModel, 'deleteMany').throws(new Error('uut error'))
await uut.clearUsage()
assert.fail('Unexpected code path')
} catch (error) {
assert.equal(error.message, 'uut error')
}
})
})
describe('#saveUsage', () => {
it('should save usage', async () => {
// Set mock data
restCalls.push({
timestamp: new Date().getTime(),
ip: 'localhost',
url: 'fakeUrl',
method: 'unit test'
})
const res = await uut.saveUsage()
assert.isTrue(res)
})
it('should handle error', async () => {
try {
restCalls.push(null)
await uut.saveUsage()
assert.fail('Unexpected code path')
} catch (error) {
console.log(error)
assert.include(error.message, 'Cannot read properties')
}
})
})
describe('#loadUsage', () => {
it('should load usage', async () => {
// Set mock data
const mockObj = {
timestamp: new Date().getTime(),
ip: 'localhost',
url: 'fakeUrl',
method: 'unit test'
}
sandbox.stub(uut.UsageModel, 'find').returns(new Array(10).fill(null).map((_, i) => (mockObj)))
const res = await uut.loadUsage()
assert.equal(restCalls.length, 10)
assert.equal(res.length, 10)
})
it('should skip error', async () => {
sandbox.stub(uut.UsageModel, 'find').throws(new Error('uut error'))
const res = await uut.loadUsage()
assert.isFalse(res)
})
})
})
+45 -20
View File
@@ -8,7 +8,6 @@ import axios from 'axios'
// Local libraries
import config from '../../config/index.js'
import User from '../../src/adapters/localdb/models/users.js'
import JsonFiles from '../../src/adapters/json-files.js'
// Hack to get __dirname back.
@@ -33,24 +32,6 @@ async function cleanDb () {
}
}
// Delete all users in the database. This ensures there is no previous state
// to confuse tests.
async function deleteAllUsers () {
try {
// Get all the users in the DB.
const users = await User.find({}, '-password')
// console.log(`users: ${JSON.stringify(users, null, 2)}`)
// Delete each user.
for (let i = 0; i < users.length; i++) {
const thisUser = users[i]
await thisUser.remove()
}
} catch (err) {
console.error('Error in test-utils.js/deleteAllUsers()')
}
}
// This function is used to create new users.
// userObj = {
// username,
@@ -170,12 +151,56 @@ async function getAdminJWT () {
throw err
}
}
// Fetches all users from the database.
async function getAllUsers () {
try {
const adminJWT = await getAdminJWT()
const options = {
method: 'GET',
url: `${LOCALHOST}/users`,
headers: {
Authorization: `Bearer ${adminJWT}`
}
}
const result = await axios(options)
return result.data.users
} catch (err) {
console.error('Error in test/utils.js/getAllUsers()', err)
throw err
}
}
// Deletes all users from the database.
async function deleteAllUsers () {
try {
const allUsers = await getAllUsers()
const adminJWT = await getAdminJWT()
for (let i = 0; i < allUsers.length; i++) {
const user = allUsers[i]
// Skip the admin user.
if (user.type === 'admin') {
continue
}
const options = {
method: 'DELETE',
url: `${LOCALHOST}/users/${user._id}`,
headers: {
Authorization: `Bearer ${adminJWT}`
}
}
await axios(options)
}
} catch (err) {
console.error('Error in test/utils.js/deleteAllUsers()', err)
throw err
}
}
export default {
cleanDb,
createUser,
loginTestUser,
loginAdminUser,
getAdminJWT,
deleteAllUsers
deleteAllUsers,
getAllUsers
}
+3 -4
View File
@@ -1,8 +1,9 @@
import mongoose from 'mongoose'
import config from '../../config/index.js'
import User from '../../src/adapters/localdb/models/users.js'
const EMAIL = 'test@test.com'
const PASSWORD = 'pass'
const EMAIL = process.env.EMAIL || 'test@test3.com'
const PASSWORD = process.env.PASSWORD || 'pass'
async function addUser () {
// Connect to the Mongo Database.
@@ -13,8 +14,6 @@ async function addUser () {
{ useNewUrlParser: true, useUnifiedTopology: true }
)
const User = require('../../src/models/users')
const userData = {
email: EMAIL,
password: PASSWORD
+1 -1
View File
@@ -1,6 +1,6 @@
import mongoose from 'mongoose'
import config from '../../config/index.js'
import User from '../../src/models/users.js'
import User from '../../src/adapters/localdb/models/users.js'
async function getUsers () {
// Connect to the Mongo Database.
+30
View File
@@ -0,0 +1,30 @@
import mongoose from 'mongoose'
import config from '../../config/index.js'
import User from '../../src/adapters/localdb/models/users.js'
async function getUsers () {
// Connect to the Mongo Database.
mongoose.Promise = global.Promise
mongoose.set('useCreateIndex', true) // Stop deprecation warning.
await mongoose.connect(
config.database,
{ useNewUrlParser: true, useUnifiedTopology: true }
)
// Find the user by email.
const user = await User.findOne({
email: 'test@test.com'
}, '-password')
// Update the users password
// user.password = 'newpassword'
// Change the user to an admin
// user.type = 'admin'
// Save the changes to the database.
await user.save()
mongoose.connection.close()
}
getUsers()