Переглянути джерело

Top selling channels (#114)

* top selling channels initial draft

* introduced node-cache for in-memory caching

* CR fixes: updated cache key, minor code fix

* upgraded performance schema

Co-authored-by: Leszek Wiesner <leszek@jsgenesis.com>

* restored env variables

* removed cache, introduced 0 periodDays option

* fixed infinite period query

Co-authored-by: Leszek Wiesner <leszek@jsgenesis.com>

* removed unused binary files

---------

Co-authored-by: Artem <Artem Slugin>
Co-authored-by: Leszek Wiesner <leszek@jsgenesis.com>
attemka 1 рік тому
батько
коміт
65221a707b

+ 1 - 1
docs/developer-guide.md

@@ -77,7 +77,7 @@ make down
 ## Commands
 
 - `make typegen` - generates event types (`src/types`) based on `typegen.json` (the archive endpoint provided in `specVersions` must be pointing to a running archive)
-- `make codegen` - granetes TypeORM models based on the [input schema](#input-schema)
+- `make codegen` - generates TypeORM models based on the [input schema](#input-schema)
 - `make dbgen` - generates database migration in `db/migrations` (PostgreSQL service must be running) based on the difference between current database schema and TypeORM models
 - `make build` - builds the code
 - `make prepare` - runs `npm install + codegen + build`

+ 175 - 138
package-lock.json

@@ -27,6 +27,7 @@
         "graphql-tools": "^8.3.11",
         "haversine-distance": "^1.2.1",
         "lodash": "^4.17.21",
+        "node-cache": "^5.1.2",
         "patch-package": "^6.5.0",
         "pg": "8.8.0",
         "type-graphql": "^1.2.0-rc.1",
@@ -4105,9 +4106,9 @@
       ]
     },
     "node_modules/@sqltools/formatter": {
-      "version": "1.2.3",
-      "resolved": "https://registry.npmjs.org/@sqltools/formatter/-/formatter-1.2.3.tgz",
-      "integrity": "sha512-O3uyB/JbkAEMZaP3YqyHH7TMnex7tWyCbCI4EfJdOCoN6HIhqdJBWTM6aCCiWQ/5f5wxjgU735QAIpJbjDvmzg=="
+      "version": "1.2.5",
+      "resolved": "https://registry.npmjs.org/@sqltools/formatter/-/formatter-1.2.5.tgz",
+      "integrity": "sha512-Uy0+khmZqUrUGm5dmMqVlnvufZRSK0FbYzVgp0UMstm+F5+W2/jnEEQyc9vo1ZR/E5ZI/B1WjjoTqBqwJL6Krw=="
     },
     "node_modules/@subsquid/archive-registry": {
       "version": "2.1.0",
@@ -5836,14 +5837,6 @@
         "node": ">=10"
       }
     },
-    "node_modules/apollo-server-core/node_modules/uuid": {
-      "version": "9.0.0",
-      "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz",
-      "integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==",
-      "bin": {
-        "uuid": "dist/bin/uuid"
-      }
-    },
     "node_modules/apollo-server-env": {
       "version": "4.2.1",
       "resolved": "https://registry.npmjs.org/apollo-server-env/-/apollo-server-env-4.2.1.tgz",
@@ -5959,7 +5952,8 @@
     "node_modules/argparse": {
       "version": "2.0.1",
       "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
-      "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="
+      "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
+      "dev": true
     },
     "node_modules/array-flatten": {
       "version": "1.1.1",
@@ -7064,18 +7058,6 @@
       "resolved": "https://registry.npmjs.org/dataloader/-/dataloader-2.2.1.tgz",
       "integrity": "sha512-Zn+tVZo1RKu120rgoe0JsRk56UiKdefPSH47QROJsMHrX8/S9UJvi5A/A6+Sbuk6rE88z5JoM/wIJ09Z7BTfYA=="
     },
-    "node_modules/date-fns": {
-      "version": "2.29.3",
-      "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.29.3.tgz",
-      "integrity": "sha512-dDCnyH2WnnKusqvZZ6+jA1O51Ibt8ZMRNkDZdyAyK4YfbDwa/cEmuztzG5pk6hqlp9aSBPYcjOlktquahGwGeA==",
-      "engines": {
-        "node": ">=0.11"
-      },
-      "funding": {
-        "type": "opencollective",
-        "url": "https://opencollective.com/date-fns"
-      }
-    },
     "node_modules/debounce": {
       "version": "1.2.1",
       "resolved": "https://registry.npmjs.org/debounce/-/debounce-1.2.1.tgz",
@@ -9945,6 +9927,7 @@
       "version": "4.1.0",
       "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
       "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
+      "dev": true,
       "dependencies": {
         "argparse": "^2.0.1"
       },
@@ -10533,14 +10516,17 @@
       }
     },
     "node_modules/mkdirp": {
-      "version": "1.0.4",
-      "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz",
-      "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==",
+      "version": "2.1.6",
+      "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-2.1.6.tgz",
+      "integrity": "sha512-+hEnITedc8LAtIP9u3HJDFIdcLV2vXP33sqLLIzkv1Db1zO/1OxbvYf0Y1OC/S/Qo5dxHXepofhmxL02PsKe+A==",
       "bin": {
-        "mkdirp": "bin/cmd.js"
+        "mkdirp": "dist/cjs/src/bin.js"
       },
       "engines": {
         "node": ">=10"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/isaacs"
       }
     },
     "node_modules/mock-socket": {
@@ -10670,6 +10656,25 @@
       "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-2.0.2.tgz",
       "integrity": "sha512-Ntyt4AIXyaLIuMHF6IOoTakB3K+RWxwtsHNRxllEoA6vPwP9o4866g6YWDLUdnucilZhmkxiHwHr11gAENw+QA=="
     },
+    "node_modules/node-cache": {
+      "version": "5.1.2",
+      "resolved": "https://registry.npmjs.org/node-cache/-/node-cache-5.1.2.tgz",
+      "integrity": "sha512-t1QzWwnk4sjLWaQAS8CHgOJ+RAfmHpxFWmc36IWTiWHQfs0w5JDMBS1b1ZxQteo0vVVuWJvIUKHDkkeK7vIGCg==",
+      "dependencies": {
+        "clone": "2.x"
+      },
+      "engines": {
+        "node": ">= 8.0.0"
+      }
+    },
+    "node_modules/node-cache/node_modules/clone": {
+      "version": "2.1.2",
+      "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz",
+      "integrity": "sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==",
+      "engines": {
+        "node": ">=0.8"
+      }
+    },
     "node_modules/node-fetch": {
       "version": "2.6.7",
       "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz",
@@ -11950,11 +11955,6 @@
       "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
       "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
     },
-    "node_modules/sax": {
-      "version": "1.2.4",
-      "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz",
-      "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw=="
-    },
     "node_modules/scrypt-js": {
       "version": "3.0.1",
       "resolved": "https://registry.npmjs.org/scrypt-js/-/scrypt-js-3.0.1.tgz",
@@ -12715,27 +12715,24 @@
       }
     },
     "node_modules/typeorm": {
-      "version": "0.3.11",
-      "resolved": "https://registry.npmjs.org/typeorm/-/typeorm-0.3.11.tgz",
-      "integrity": "sha512-pzdOyWbVuz/z8Ww6gqvBW4nylsM0KLdUCDExr2gR20/x1khGSVxQkjNV/3YqliG90jrWzrknYbYscpk8yxFJVg==",
+      "version": "0.3.15",
+      "resolved": "https://registry.npmjs.org/typeorm/-/typeorm-0.3.15.tgz",
+      "integrity": "sha512-R4JSw8QjDP1W+ypeRz/XrCXIqubrLSnNAzJAp9EQSQIPHTv+YmUHZis8g08lOwFpuhqL9m8jkPSz8GWEKlU/ow==",
       "dependencies": {
-        "@sqltools/formatter": "^1.2.2",
-        "app-root-path": "^3.0.0",
+        "@sqltools/formatter": "^1.2.5",
+        "app-root-path": "^3.1.0",
         "buffer": "^6.0.3",
-        "chalk": "^4.1.0",
+        "chalk": "^4.1.2",
         "cli-highlight": "^2.1.11",
-        "date-fns": "^2.28.0",
-        "debug": "^4.3.3",
-        "dotenv": "^16.0.0",
-        "glob": "^7.2.0",
-        "js-yaml": "^4.1.0",
-        "mkdirp": "^1.0.4",
+        "debug": "^4.3.4",
+        "dotenv": "^16.0.3",
+        "glob": "^8.1.0",
+        "mkdirp": "^2.1.3",
         "reflect-metadata": "^0.1.13",
         "sha.js": "^2.4.11",
-        "tslib": "^2.3.1",
-        "uuid": "^8.3.2",
-        "xml2js": "^0.4.23",
-        "yargs": "^17.3.1"
+        "tslib": "^2.5.0",
+        "uuid": "^9.0.0",
+        "yargs": "^17.6.2"
       },
       "bin": {
         "typeorm": "cli.js",
@@ -12754,9 +12751,9 @@
         "better-sqlite3": "^7.1.2 || ^8.0.0",
         "hdb-pool": "^0.1.6",
         "ioredis": "^5.0.4",
-        "mongodb": "^3.6.0",
-        "mssql": "^7.3.0",
-        "mysql2": "^2.2.5",
+        "mongodb": "^5.2.0",
+        "mssql": "^9.1.1",
+        "mysql2": "^2.2.5 || ^3.0.1",
         "oracledb": "^5.1.0",
         "pg": "^8.5.1",
         "pg-native": "^3.0.0",
@@ -12821,6 +12818,14 @@
         }
       }
     },
+    "node_modules/typeorm/node_modules/brace-expansion": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
+      "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
+      "dependencies": {
+        "balanced-match": "^1.0.0"
+      }
+    },
     "node_modules/typeorm/node_modules/debug": {
       "version": "4.3.4",
       "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
@@ -12837,11 +12842,45 @@
         }
       }
     },
+    "node_modules/typeorm/node_modules/glob": {
+      "version": "8.1.0",
+      "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz",
+      "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==",
+      "dependencies": {
+        "fs.realpath": "^1.0.0",
+        "inflight": "^1.0.4",
+        "inherits": "2",
+        "minimatch": "^5.0.1",
+        "once": "^1.3.0"
+      },
+      "engines": {
+        "node": ">=12"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/isaacs"
+      }
+    },
+    "node_modules/typeorm/node_modules/minimatch": {
+      "version": "5.1.6",
+      "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz",
+      "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==",
+      "dependencies": {
+        "brace-expansion": "^2.0.1"
+      },
+      "engines": {
+        "node": ">=10"
+      }
+    },
     "node_modules/typeorm/node_modules/ms": {
       "version": "2.1.2",
       "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
       "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
     },
+    "node_modules/typeorm/node_modules/tslib": {
+      "version": "2.5.0",
+      "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz",
+      "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg=="
+    },
     "node_modules/typescript": {
       "version": "4.8.2",
       "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.8.2.tgz",
@@ -13047,9 +13086,9 @@
       }
     },
     "node_modules/uuid": {
-      "version": "8.3.2",
-      "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
-      "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==",
+      "version": "9.0.0",
+      "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz",
+      "integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==",
       "bin": {
         "uuid": "dist/bin/uuid"
       }
@@ -13324,26 +13363,6 @@
         }
       }
     },
-    "node_modules/xml2js": {
-      "version": "0.4.23",
-      "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.23.tgz",
-      "integrity": "sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug==",
-      "dependencies": {
-        "sax": ">=0.6.0",
-        "xmlbuilder": "~11.0.0"
-      },
-      "engines": {
-        "node": ">=4.0.0"
-      }
-    },
-    "node_modules/xmlbuilder": {
-      "version": "11.0.1",
-      "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz",
-      "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==",
-      "engines": {
-        "node": ">=4.0"
-      }
-    },
     "node_modules/xss": {
       "version": "1.0.14",
       "resolved": "https://registry.npmjs.org/xss/-/xss-1.0.14.tgz",
@@ -13421,9 +13440,9 @@
       "dev": true
     },
     "node_modules/yargs": {
-      "version": "17.6.0",
-      "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.6.0.tgz",
-      "integrity": "sha512-8H/wTDqlSwoSnScvV2N/JHfLWOKuh5MVla9hqLjK3nsfyy6Y4kDSYSvkU5YCUEPOSnRXfIyx3Sq+B/IWudTo4g==",
+      "version": "17.7.1",
+      "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.1.tgz",
+      "integrity": "sha512-cwiTb08Xuv5fqF4AovYacTFNxk62th7LKJ6BL9IGUpTJrWoU7/7WdQGTP2SjKf1dUNBGzDd28p/Yfs/GI6JrLw==",
       "dependencies": {
         "cliui": "^8.0.1",
         "escalade": "^3.1.1",
@@ -13431,7 +13450,7 @@
         "require-directory": "^2.1.1",
         "string-width": "^4.2.3",
         "y18n": "^5.0.5",
-        "yargs-parser": "^21.0.0"
+        "yargs-parser": "^21.1.1"
       },
       "engines": {
         "node": ">=12"
@@ -16683,9 +16702,9 @@
       "integrity": "sha512-ZxOhsSyxYwLJj3pLZCefNitxsj093tb2vq90mp2txoYeBqbcjDjqFhyM8eUjq/uFm6zJ+mUuqxlS2FkuSY1MTA=="
     },
     "@sqltools/formatter": {
-      "version": "1.2.3",
-      "resolved": "https://registry.npmjs.org/@sqltools/formatter/-/formatter-1.2.3.tgz",
-      "integrity": "sha512-O3uyB/JbkAEMZaP3YqyHH7TMnex7tWyCbCI4EfJdOCoN6HIhqdJBWTM6aCCiWQ/5f5wxjgU735QAIpJbjDvmzg=="
+      "version": "1.2.5",
+      "resolved": "https://registry.npmjs.org/@sqltools/formatter/-/formatter-1.2.5.tgz",
+      "integrity": "sha512-Uy0+khmZqUrUGm5dmMqVlnvufZRSK0FbYzVgp0UMstm+F5+W2/jnEEQyc9vo1ZR/E5ZI/B1WjjoTqBqwJL6Krw=="
     },
     "@subsquid/archive-registry": {
       "version": "2.1.0",
@@ -18022,11 +18041,6 @@
           "requires": {
             "yallist": "^4.0.0"
           }
-        },
-        "uuid": {
-          "version": "9.0.0",
-          "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz",
-          "integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg=="
         }
       }
     },
@@ -18105,7 +18119,8 @@
     "argparse": {
       "version": "2.0.1",
       "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
-      "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="
+      "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
+      "dev": true
     },
     "array-flatten": {
       "version": "1.1.1",
@@ -18965,11 +18980,6 @@
       "resolved": "https://registry.npmjs.org/dataloader/-/dataloader-2.2.1.tgz",
       "integrity": "sha512-Zn+tVZo1RKu120rgoe0JsRk56UiKdefPSH47QROJsMHrX8/S9UJvi5A/A6+Sbuk6rE88z5JoM/wIJ09Z7BTfYA=="
     },
-    "date-fns": {
-      "version": "2.29.3",
-      "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.29.3.tgz",
-      "integrity": "sha512-dDCnyH2WnnKusqvZZ6+jA1O51Ibt8ZMRNkDZdyAyK4YfbDwa/cEmuztzG5pk6hqlp9aSBPYcjOlktquahGwGeA=="
-    },
     "debounce": {
       "version": "1.2.1",
       "resolved": "https://registry.npmjs.org/debounce/-/debounce-1.2.1.tgz",
@@ -21095,6 +21105,7 @@
       "version": "4.1.0",
       "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
       "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
+      "dev": true,
       "requires": {
         "argparse": "^2.0.1"
       }
@@ -21546,9 +21557,9 @@
       "integrity": "sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g=="
     },
     "mkdirp": {
-      "version": "1.0.4",
-      "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz",
-      "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw=="
+      "version": "2.1.6",
+      "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-2.1.6.tgz",
+      "integrity": "sha512-+hEnITedc8LAtIP9u3HJDFIdcLV2vXP33sqLLIzkv1Db1zO/1OxbvYf0Y1OC/S/Qo5dxHXepofhmxL02PsKe+A=="
     },
     "mock-socket": {
       "version": "9.2.1",
@@ -21659,6 +21670,21 @@
       "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-2.0.2.tgz",
       "integrity": "sha512-Ntyt4AIXyaLIuMHF6IOoTakB3K+RWxwtsHNRxllEoA6vPwP9o4866g6YWDLUdnucilZhmkxiHwHr11gAENw+QA=="
     },
+    "node-cache": {
+      "version": "5.1.2",
+      "resolved": "https://registry.npmjs.org/node-cache/-/node-cache-5.1.2.tgz",
+      "integrity": "sha512-t1QzWwnk4sjLWaQAS8CHgOJ+RAfmHpxFWmc36IWTiWHQfs0w5JDMBS1b1ZxQteo0vVVuWJvIUKHDkkeK7vIGCg==",
+      "requires": {
+        "clone": "2.x"
+      },
+      "dependencies": {
+        "clone": {
+          "version": "2.1.2",
+          "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz",
+          "integrity": "sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w=="
+        }
+      }
+    },
     "node-fetch": {
       "version": "2.6.7",
       "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz",
@@ -22583,11 +22609,6 @@
       "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
       "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
     },
-    "sax": {
-      "version": "1.2.4",
-      "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz",
-      "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw=="
-    },
     "scrypt-js": {
       "version": "3.0.1",
       "resolved": "https://registry.npmjs.org/scrypt-js/-/scrypt-js-3.0.1.tgz",
@@ -23166,29 +23187,34 @@
       }
     },
     "typeorm": {
-      "version": "0.3.11",
-      "resolved": "https://registry.npmjs.org/typeorm/-/typeorm-0.3.11.tgz",
-      "integrity": "sha512-pzdOyWbVuz/z8Ww6gqvBW4nylsM0KLdUCDExr2gR20/x1khGSVxQkjNV/3YqliG90jrWzrknYbYscpk8yxFJVg==",
+      "version": "0.3.15",
+      "resolved": "https://registry.npmjs.org/typeorm/-/typeorm-0.3.15.tgz",
+      "integrity": "sha512-R4JSw8QjDP1W+ypeRz/XrCXIqubrLSnNAzJAp9EQSQIPHTv+YmUHZis8g08lOwFpuhqL9m8jkPSz8GWEKlU/ow==",
       "requires": {
-        "@sqltools/formatter": "^1.2.2",
-        "app-root-path": "^3.0.0",
+        "@sqltools/formatter": "^1.2.5",
+        "app-root-path": "^3.1.0",
         "buffer": "^6.0.3",
-        "chalk": "^4.1.0",
+        "chalk": "^4.1.2",
         "cli-highlight": "^2.1.11",
-        "date-fns": "^2.28.0",
-        "debug": "^4.3.3",
-        "dotenv": "^16.0.0",
-        "glob": "^7.2.0",
-        "js-yaml": "^4.1.0",
-        "mkdirp": "^1.0.4",
+        "debug": "^4.3.4",
+        "dotenv": "^16.0.3",
+        "glob": "^8.1.0",
+        "mkdirp": "^2.1.3",
         "reflect-metadata": "^0.1.13",
         "sha.js": "^2.4.11",
-        "tslib": "^2.3.1",
-        "uuid": "^8.3.2",
-        "xml2js": "^0.4.23",
-        "yargs": "^17.3.1"
+        "tslib": "^2.5.0",
+        "uuid": "^9.0.0",
+        "yargs": "^17.6.2"
       },
       "dependencies": {
+        "brace-expansion": {
+          "version": "2.0.1",
+          "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
+          "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
+          "requires": {
+            "balanced-match": "^1.0.0"
+          }
+        },
         "debug": {
           "version": "4.3.4",
           "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
@@ -23197,10 +23223,35 @@
             "ms": "2.1.2"
           }
         },
+        "glob": {
+          "version": "8.1.0",
+          "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz",
+          "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==",
+          "requires": {
+            "fs.realpath": "^1.0.0",
+            "inflight": "^1.0.4",
+            "inherits": "2",
+            "minimatch": "^5.0.1",
+            "once": "^1.3.0"
+          }
+        },
+        "minimatch": {
+          "version": "5.1.6",
+          "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz",
+          "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==",
+          "requires": {
+            "brace-expansion": "^2.0.1"
+          }
+        },
         "ms": {
           "version": "2.1.2",
           "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
           "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
+        },
+        "tslib": {
+          "version": "2.5.0",
+          "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz",
+          "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg=="
         }
       }
     },
@@ -23346,9 +23397,9 @@
       "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA=="
     },
     "uuid": {
-      "version": "8.3.2",
-      "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
-      "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg=="
+      "version": "9.0.0",
+      "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz",
+      "integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg=="
     },
     "v8-compile-cache-lib": {
       "version": "3.0.1",
@@ -23550,20 +23601,6 @@
       "integrity": "sha512-kU62emKIdKVeEIOIKVegvqpXMSTAMLJozpHZaJNDYqBjzlSYXQGviYwN1osDLJ9av68qHd4a2oSjd7yD4pacig==",
       "requires": {}
     },
-    "xml2js": {
-      "version": "0.4.23",
-      "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.23.tgz",
-      "integrity": "sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug==",
-      "requires": {
-        "sax": ">=0.6.0",
-        "xmlbuilder": "~11.0.0"
-      }
-    },
-    "xmlbuilder": {
-      "version": "11.0.1",
-      "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz",
-      "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA=="
-    },
     "xss": {
       "version": "1.0.14",
       "resolved": "https://registry.npmjs.org/xss/-/xss-1.0.14.tgz",
@@ -23625,9 +23662,9 @@
       "dev": true
     },
     "yargs": {
-      "version": "17.6.0",
-      "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.6.0.tgz",
-      "integrity": "sha512-8H/wTDqlSwoSnScvV2N/JHfLWOKuh5MVla9hqLjK3nsfyy6Y4kDSYSvkU5YCUEPOSnRXfIyx3Sq+B/IWudTo4g==",
+      "version": "17.7.1",
+      "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.1.tgz",
+      "integrity": "sha512-cwiTb08Xuv5fqF4AovYacTFNxk62th7LKJ6BL9IGUpTJrWoU7/7WdQGTP2SjKf1dUNBGzDd28p/Yfs/GI6JrLw==",
       "requires": {
         "cliui": "^8.0.1",
         "escalade": "^3.1.1",
@@ -23635,7 +23672,7 @@
         "require-directory": "^2.1.1",
         "string-width": "^4.2.3",
         "y18n": "^5.0.5",
-        "yargs-parser": "^21.0.0"
+        "yargs-parser": "^21.1.1"
       }
     },
     "yargs-parser": {

+ 15 - 1
src/server-extension/resolvers/ChannelsResolver/index.ts

@@ -14,13 +14,15 @@ import {
   ChannelFollowResult,
   ChannelUnfollowResult,
   ChannelNftCollectorsOrderByInput,
+  TopSellingChannelsArgs,
+  TopSellingChannelsResult,
 } from './types'
 import { GraphQLResolveInfo } from 'graphql'
 import { Context } from '@subsquid/openreader/lib/context'
 import { Channel, ChannelFollow, Report } from '../../../model'
 import { randomAsHex } from '@polkadot/util-crypto'
 import { extendClause, withHiddenEntities } from '../../../utils/sql'
-import { buildExtendedChannelsQuery } from './utils'
+import { buildExtendedChannelsQuery, buildTopSellingChannelsQuery } from './utils'
 import { parseAnyTree } from '@subsquid/openreader/lib/opencrud/tree'
 import { getResolveTree } from '@subsquid/openreader/lib/util/resolve-tree'
 import { ListQuery } from '@subsquid/openreader/lib/sql/query'
@@ -76,6 +78,18 @@ export class ChannelsResolver {
     return result
   }
 
+  @Query(() => [TopSellingChannelsResult])
+  async topSellingChannels(
+    @Args() args: TopSellingChannelsArgs,
+    @Info() info: GraphQLResolveInfo,
+    @Ctx() ctx: Context
+  ): Promise<TopSellingChannelsResult[]> {
+    const listQuery = buildTopSellingChannelsQuery(args, info, ctx)
+
+    const result = await ctx.openreader.executeQuery(listQuery)
+    return result
+  }
+
   @Query(() => [ChannelNftCollector])
   async channelNftCollectors(
     @Args() args: ChannelNftCollectorsArgs,

+ 21 - 0
src/server-extension/resolvers/ChannelsResolver/types.ts

@@ -12,6 +12,15 @@ export class ExtendedChannel {
   activeVideosCount!: number
 }
 
+@ObjectType()
+export class TopSellingChannelsResult {
+  @Field(() => Channel, { nullable: false })
+  channel!: Channel
+
+  @Field(() => String, { nullable: false })
+  amount!: string
+}
+
 @ObjectType()
 export class ChannelNftCollector {
   @Field(() => Membership, { nullable: false })
@@ -57,6 +66,18 @@ export class MostRecentChannelsArgs {
   resultsLimit?: number
 }
 
+@ArgsType()
+export class TopSellingChannelsArgs {
+  @Field(() => ExtendedChannelWhereInput, { nullable: true })
+  where?: ExtendedChannelWhereInput
+
+  @Field(() => Int, { nullable: false })
+  limit!: number
+
+  @Field(() => Int, { nullable: false })
+  periodDays!: number
+}
+
 export enum ChannelNftCollectorsOrderByInput {
   amount_ASC,
   amount_DESC,

+ 75 - 1
src/server-extension/resolvers/ChannelsResolver/utils.ts

@@ -1,5 +1,5 @@
 import 'reflect-metadata'
-import { ExtendedChannelsArgs } from './types'
+import { ExtendedChannelsArgs, TopSellingChannelsArgs } from './types'
 import { parseSqlArguments, parseAnyTree } from '@subsquid/openreader/lib/opencrud/tree'
 import { getResolveTree } from '@subsquid/openreader/lib/util/resolve-tree'
 import { ListQuery } from '@subsquid/openreader/lib/sql/query'
@@ -105,3 +105,77 @@ export function buildExtendedChannelsQuery(
 
   return listQuery
 }
+
+export function buildTopSellingChannelsQuery(
+  args: TopSellingChannelsArgs,
+  info: GraphQLResolveInfo,
+  ctx: Context
+) {
+  const roundedDate = new Date().setMinutes(0, 0, 0)
+
+  const tree = getResolveTree(info)
+
+  // Extract subsquid-supported Channel sql args
+  const sqlArgs = parseSqlArguments(model, 'Channel', {
+    ...args,
+    where: args.where?.channel, // only supported WHERE part
+  })
+
+  // Extract subsquid-supported Channel fields
+  const channelSubTree = tree.fieldsByTypeName.TopSellingChannelsResult.channel
+  const channelFields = parseAnyTree(model, 'Channel', info.schema, channelSubTree)
+
+  // Generate query using subsquid's ListQuery
+  const listQuery = new ListQuery(model, ctx.openreader.dialect, 'Channel', channelFields, sqlArgs)
+  let listQuerySql = listQuery.sql
+
+  // Count NFT sells from the auctions and buy now events and add them to the query
+  listQuerySql = extendClause(listQuerySql, 'SELECT', '"top_selling_channels"."amount"')
+
+  listQuerySql = extendClause(
+    listQuerySql,
+    'FROM',
+    `
+    INNER JOIN (
+      SELECT
+        (SUM((COALESCE(event.data->>'price', '0')::bigint)) + SUM(COALESCE(winning_bid.amount, 0))) AS "amount",
+        "data"->'previousNftOwner'->>'channel' AS "channel_id"
+      FROM "event"
+      LEFT JOIN bid AS winning_bid ON "data"->>'winningBid' = winning_bid.id
+      WHERE
+      ${
+        args?.periodDays > 0
+          ? ` "event"."timestamp" > '${new Date(
+              roundedDate - args.periodDays * 24 * 60 * 60 * 1000
+            ).toISOString()}' AND`
+          : ''
+      }
+        "event"."data"->>'isTypeOf' IN (
+          'NftBoughtEventData',
+          'EnglishAuctionSettledEventData',
+          'BidMadeCompletingAuctionEventData',
+          'OpenAuctionBidAcceptedEventData'
+        ) AND
+        "data"->'previousNftOwner'->>'channel' IS NOT NULL
+        GROUP BY "event"."data"->'previousNftOwner'->>'channel'
+    ) AS "top_selling_channels" ON "top_selling_channels"."channel_id" = "channel"."id"
+    `,
+    ''
+  )
+
+  listQuerySql = extendClause(listQuerySql, 'ORDER BY', '"top_selling_channels"."amount" DESC')
+  ;(listQuery as { sql: string }).sql = listQuerySql
+
+  // Attach channels earnings amounts in the response
+  const oldListQMap = listQuery.map.bind(listQuery)
+  listQuery.map = (rows: unknown[][]) => {
+    const sellAmounts: unknown[] = []
+    for (const row of rows) {
+      sellAmounts.push(row.pop())
+    }
+    const channelsMapped = oldListQMap(rows)
+    return channelsMapped.map((channel, i) => ({ channel, amount: sellAmounts[i] }))
+  }
+
+  return listQuery
+}

+ 23 - 0
src/tests/v2/generated/queries.ts

@@ -137,6 +137,18 @@ export type GetPayloadDataObjectIdByCommitmentQuery = {
   }>
 }
 
+export type GetTopSellingChannelsQueryVariables = Types.Exact<{
+  where?: Types.Maybe<Types.ExtendedChannelWhereInput>
+  limit: Types.Scalars['Int']
+  periodDays: Types.Scalars['Int']
+}>
+
+export type GetTopSellingChannelsQuery = {
+  topSellingChannels?: Types.Maybe<
+    Array<Types.Maybe<{ amount: number; channel: BasicChannelFieldsFragment }>>
+  >
+}
+
 export type ReportChannelMutationVariables = Types.Exact<{
   channelId: Types.Scalars['String']
   rationale: Types.Scalars['String']
@@ -2692,6 +2704,17 @@ export const GetPayloadDataObjectIdByCommitment = gql`
     }
   }
 `
+export const GetTopSellingChannels = gql`
+  query GetTopSellingChannels($where: ExtendedChannelWhereInput, $limit: Int!, $periodDays: Int!) {
+    topSellingChannels(where: $where, limit: $limit, periodDays: $periodDays) {
+      channel {
+        ...BasicChannelFields
+      }
+      amount
+    }
+  }
+  ${BasicChannelFields}
+`
 export const ReportChannel = gql`
   mutation ReportChannel($channelId: String!, $rationale: String!) {
     reportChannel(channelId: $channelId, rationale: $rationale) {

+ 12 - 0
src/tests/v2/generated/schema.ts

@@ -4362,6 +4362,7 @@ export type Query = {
   storageDataObjectByUniqueInput?: Maybe<StorageDataObject>
   storageDataObjects: Array<StorageDataObject>
   storageDataObjectsConnection: StorageDataObjectsConnection
+  topSellingChannels?: Maybe<Array<Maybe<TopSellingChannelsResult>>>
   videoById?: Maybe<Video>
   /** @deprecated Use videoById */
   videoByUniqueInput?: Maybe<Video>
@@ -5148,6 +5149,12 @@ export type QueryStorageDataObjectsConnectionArgs = {
   where?: Maybe<StorageDataObjectWhereInput>
 }
 
+export type QueryTopSellingChannelsArgs = {
+  limit: Scalars['Int']
+  periodDays: Scalars['Int']
+  where?: Maybe<ExtendedChannelWhereInput>
+}
+
 export type QueryVideoByIdArgs = {
   id: Scalars['String']
 }
@@ -6750,6 +6757,11 @@ export type SubscriptionVideosArgs = {
   where?: Maybe<VideoWhereInput>
 }
 
+export type TopSellingChannelsResult = {
+  amount: Scalars['Int']
+  channel: Channel
+}
+
 export type TransactionalStatus =
   | TransactionalStatusAuction
   | TransactionalStatusBuyNow

+ 9 - 0
src/tests/v2/queries/atlasQueries/channels.graphql

@@ -158,6 +158,15 @@ query GetPayloadDataObjectIdByCommitment($commitment: String!) {
   }
 }
 
+query GetTopSellingChannels($where: ExtendedChannelWhereInput, $limit: Int!, $periodDays: Int!) {
+  topSellingChannels(where: $where, limit: $limit, periodDays: $periodDays) {
+    channel {
+      ...BasicChannelFields
+    }
+    amount
+  }
+}
+
 # CHANGE: `ID` is now `String`
 mutation ReportChannel($channelId: String!, $rationale: String!) {
   reportChannel(channelId: $channelId, rationale: $rationale) {

+ 10 - 0
src/tests/v2/schema.graphql

@@ -353,6 +353,11 @@ type ExtendedChannel {
   activeVideosCount: Int!
 }
 
+type TopSellingChannelsResult {
+  channel: Channel!
+  amount: Int!
+}
+
 type ChannelNftCollector {
   member: Membership!
   amount: Int!
@@ -657,6 +662,11 @@ type Query {
     mostRecentLimit: Int!
     resultsLimit: Int
   ): [ExtendedChannel!]!
+  topSellingChannels(
+    where: ExtendedChannelWhereInput
+    limit: Int!
+    periodDays: Int!
+  ): [TopSellingChannelsResult]
   channelNftCollectors(
     channelId: String!
     orderBy: ChannelNftCollectorsOrderByInput