diff --git a/.env b/.env index 7d274f0..8d68dfa 100644 --- a/.env +++ b/.env @@ -1,3 +1,5 @@ PORT=8000 WEATHER_API_KEY=de18eeaec53e4caca18170027252507 -DB_PASSWORD=ayobamidavid \ No newline at end of file +DB_PASSWORD=ayobamidavid +STRIPE_SECRET_KEY=sk_test_51IWQUwH8oljXErmds28KftkL6o6jYIcPgYbBdfEmCPSuAlIh0fgoS4NADcCmsIZbdQ3p5nbAeCOcGkSmo38U9BIe00BdOenrqo +STRIPE_PUBLIC_KEY=pk_test_51IWQUwH8oljXErmdg6L4MhsuB6tDdmumlHFfyNaopty2U27pmRcqMX1c868zn838lGQtU1eYV6bKRSQtMFWf36VT00aNsvnTOE \ No newline at end of file diff --git a/models/chat.js b/models/chat.js new file mode 100644 index 0000000..7b6408a --- /dev/null +++ b/models/chat.js @@ -0,0 +1,26 @@ +module.exports = (sequelize, DataTypes) => { + const chat = sequelize.define( + "chat", + { + id: { + type: DataTypes.INTEGER, + primaryKey: true, + autoIncrement: true, + }, + create_at: { + type: DataTypes.DATE, + defaultValue: DataTypes.NOW, + }, + chat_messages: { + type: DataTypes.TEXT, + allowNull: false, + }, + }, + { + timestamps: false, + freezeTableName: true, + tableName: "chat", + } + ); + return chat; +}; diff --git a/models/flow.js b/models/flow.js new file mode 100644 index 0000000..1dadef6 --- /dev/null +++ b/models/flow.js @@ -0,0 +1,30 @@ +module.exports = (sequelize, DataTypes) => { + const flow = sequelize.define( + "flow", + { + id: { + type: DataTypes.INTEGER, + primaryKey: true, + autoIncrement: true, + }, + name: { + type: DataTypes.STRING, + allowNull: false, + }, + description: { + type: DataTypes.TEXT, + allowNull: true, + }, + created_at: { + type: DataTypes.DATE, + defaultValue: DataTypes.NOW, + }, + }, + { + timestamps: false, + freezeTableName: true, + tableName: "flow", + } + ); + return flow; +}; diff --git a/models/flow_log.js b/models/flow_log.js new file mode 100644 index 0000000..ae2a452 --- /dev/null +++ b/models/flow_log.js @@ -0,0 +1,39 @@ +module.exports = (sequelize, DataTypes) => { + const flow_log = sequelize.define( + "flow_log", + { + id: { + type: DataTypes.INTEGER, + primaryKey: true, + autoIncrement: true, + }, + flow_id: { + type: DataTypes.INTEGER, + allowNull: false, + }, + task_id: { + type: DataTypes.INTEGER, + allowNull: false, + }, + result: { + type: DataTypes.TEXT, + allowNull: true, + }, + status: { + type: DataTypes.STRING, + allowNull: false, + defaultValue: "pending", + }, + created_at: { + type: DataTypes.DATE, + defaultValue: DataTypes.NOW, + }, + }, + { + timestamps: false, + freezeTableName: true, + tableName: "flow_logs", + } + ); + return flow_log; +}; diff --git a/models/task.js b/models/task.js new file mode 100644 index 0000000..0b566c8 --- /dev/null +++ b/models/task.js @@ -0,0 +1,42 @@ +module.exports = (sequelize, DataTypes) => { + const task = sequelize.define( + "task", + { + id: { + type: DataTypes.INTEGER, + primaryKey: true, + autoIncrement: true, + }, + flow_id: { + type: DataTypes.INTEGER, + allowNull: false, + references: { + model: "flow", + key: "id", + }, + }, + action_type: { + type: DataTypes.STRING, + allowNull: false, + }, + input_data: { + type: DataTypes.TEXT, + allowNull: false, + }, + order_index: { + type: DataTypes.INTEGER, + allowNull: false, + }, + created_at: { + type: DataTypes.DATE, + defaultValue: DataTypes.NOW, + }, + }, + { + timestamps: false, + freezeTableName: true, + tableName: "task", + } + ); + return task; +}; diff --git a/models/upload.js b/models/upload.js new file mode 100644 index 0000000..d0f5592 --- /dev/null +++ b/models/upload.js @@ -0,0 +1,34 @@ +module.exports = (sequelize, DataTypes) => { + const upload = sequelize.define( + "upload", + { + id: { + type: DataTypes.INTEGER, + primaryKey: true, + autoIncrement: true, + }, + filename: { + type: DataTypes.STRING, + allowNull: false, + }, + mimetype: { + type: DataTypes.STRING, + allowNull: false, + }, + path: { + type: DataTypes.STRING, + allowNull: false, + }, + created_at: { + type: DataTypes.DATE, + defaultValue: DataTypes.NOW, + }, + }, + { + timestamps: false, + freezeTableName: true, + tableName: "upload", + } + ); + return upload; +}; diff --git a/package-lock.json b/package-lock.json index d33da90..a054deb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,11 +16,14 @@ "http-errors": "~1.6.3", "mariadb": "^3.0.1", "morgan": "~1.9.1", + "multer": "^2.0.2", "mysql2": "^2.3.3", "node-fetch": "^2.7.0", "ol": "^7.1.0", "pug": "^3.0.2", + "redis": "^5.6.1", "sequelize": "^6.21.6", + "stripe": "^18.3.0", "xmlbuilder2": "^3.1.1" }, "devDependencies": { @@ -539,6 +542,66 @@ "resolved": "https://registry.npmjs.org/@petamoriken/float16/-/float16-3.6.6.tgz", "integrity": "sha512-3MUulwMtsdCA9lw8a/Kc0XDBJJVCkYTQ5aGd+///TbfkOMXoOGAzzoiYKwPEsLYZv7He7fKJ/mCacqKOO7REyg==" }, + "node_modules/@redis/bloom": { + "version": "5.6.1", + "resolved": "https://registry.npmjs.org/@redis/bloom/-/bloom-5.6.1.tgz", + "integrity": "sha512-5/22U76IMEfn6TeZ+uvjXspHw+ykBF0kpBa8xouzeHaQMXs/auqBUOEYzU2VKYDvnd2RSpPTyIg82oB7PpUgLg==", + "license": "MIT", + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "@redis/client": "^5.6.1" + } + }, + "node_modules/@redis/client": { + "version": "5.6.1", + "resolved": "https://registry.npmjs.org/@redis/client/-/client-5.6.1.tgz", + "integrity": "sha512-bWHmSFIJ5w1Y4aHsYs46XMDHKQsBHFRhNcllYaBxz2Zl+lu+gbm5yI9BqxvKh48bLTs/Wx1Kns0gN2WIasE8MA==", + "license": "MIT", + "dependencies": { + "cluster-key-slot": "1.1.2" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@redis/json": { + "version": "5.6.1", + "resolved": "https://registry.npmjs.org/@redis/json/-/json-5.6.1.tgz", + "integrity": "sha512-cTggVzPIVuiFeXcEcnTRiUzV7rmUvM9KUYxWiHyjsAVACTEUe4ifKkvzrij0H/z3ammU5tfGACffDB3olBwtVA==", + "license": "MIT", + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "@redis/client": "^5.6.1" + } + }, + "node_modules/@redis/search": { + "version": "5.6.1", + "resolved": "https://registry.npmjs.org/@redis/search/-/search-5.6.1.tgz", + "integrity": "sha512-+eOjx8O2YoKygjqkLpTHqcAq0zKLjior+ee2tRBx/3RSf1+OHxiC9Y6NstshQpvB1XHqTw9n7+f0+MsRJZrp0g==", + "license": "MIT", + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "@redis/client": "^5.6.1" + } + }, + "node_modules/@redis/time-series": { + "version": "5.6.1", + "resolved": "https://registry.npmjs.org/@redis/time-series/-/time-series-5.6.1.tgz", + "integrity": "sha512-sd3q4jMJdoSO2akw1L9NrdFI1JJ6zeMgMUoTh4a34p9sY3AnOI4aDLCecy8L2IcPAP1oNR3TbLFJiCJDQ35QTA==", + "license": "MIT", + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "@redis/client": "^5.6.1" + } + }, "node_modules/@tailwindcss/cli": { "version": "4.1.11", "resolved": "https://registry.npmjs.org/@tailwindcss/cli/-/cli-4.1.11.tgz", @@ -858,6 +921,12 @@ "node": ">= 0.6" } }, + "node_modules/append-field": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/append-field/-/append-field-1.0.0.tgz", + "integrity": "sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw==", + "license": "MIT" + }, "node_modules/argparse": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", @@ -1002,6 +1071,23 @@ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "license": "MIT" + }, + "node_modules/busboy": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", + "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==", + "dependencies": { + "streamsearch": "^1.1.0" + }, + "engines": { + "node": ">=10.16.0" + } + }, "node_modules/bytes": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", @@ -1022,6 +1108,35 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/caniuse-lite": { "version": "1.0.30001727", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001727.tgz", @@ -1052,6 +1167,30 @@ "node": ">=18" } }, + "node_modules/cluster-key-slot": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/cluster-key-slot/-/cluster-key-slot-1.1.2.tgz", + "integrity": "sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==", + "license": "Apache-2.0", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/concat-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-2.0.0.tgz", + "integrity": "sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==", + "engines": [ + "node >= 6.0" + ], + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.0.2", + "typedarray": "^0.0.6" + } + }, "node_modules/content-disposition": { "version": "0.5.2", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz", @@ -1146,6 +1285,20 @@ "resolved": "https://registry.npmjs.org/dottie/-/dottie-2.0.2.tgz", "integrity": "sha512-fmrwR04lsniq/uSr8yikThDTrM7epXHBAAjH9TbeH3rEA8tdCO7mRzB9hdmdGyJCxF8KERo9CITcm3kGuoyMhg==" }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/earcut": { "version": "2.2.4", "resolved": "https://registry.npmjs.org/earcut/-/earcut-2.2.4.tgz", @@ -1184,6 +1337,36 @@ "node": ">=10.13.0" } }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/escalade": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", @@ -1328,9 +1511,13 @@ } }, "node_modules/function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, "node_modules/generate-function": { "version": "2.3.1", @@ -1359,13 +1546,49 @@ } }, "node_modules/get-intrinsic": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.3.tgz", - "integrity": "sha512-QJVz1Tj7MS099PevUG5jvnt9tSkXN8K14dxQlikJuPt4uD9hHAHjLyLBiLR5zELelBdD9QNRAXZzsJx0WaDL9A==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", "dependencies": { - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.3" + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -1389,9 +1612,10 @@ } }, "node_modules/has-symbols": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", - "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", "engines": { "node": ">= 0.4" }, @@ -1413,6 +1637,18 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/http-errors": { "version": "1.6.3", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", @@ -1897,6 +2133,15 @@ "node": ">=0.10.0" } }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", @@ -2047,6 +2292,36 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" }, + "node_modules/multer": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/multer/-/multer-2.0.2.tgz", + "integrity": "sha512-u7f2xaZ/UG8oLXHvtF/oWTRvT44p9ecwBBqTwgJVq0+4BW1g8OW01TyMEGWBHbyMOYVHXslaut7qEQ1meATXgw==", + "license": "MIT", + "dependencies": { + "append-field": "^1.0.0", + "busboy": "^1.6.0", + "concat-stream": "^2.0.0", + "mkdirp": "^0.5.6", + "object-assign": "^4.1.1", + "type-is": "^1.6.18", + "xtend": "^4.0.2" + }, + "engines": { + "node": ">= 10.16.0" + } + }, + "node_modules/multer/node_modules/mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "license": "MIT", + "dependencies": { + "minimist": "^1.2.6" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, "node_modules/mysql2": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/mysql2/-/mysql2-2.3.3.tgz", @@ -2178,6 +2453,18 @@ "node": ">=0.10.0" } }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/ol": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/ol/-/ol-7.1.0.tgz", @@ -2584,6 +2871,36 @@ "quickselect": "^2.0.0" } }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/redis": { + "version": "5.6.1", + "resolved": "https://registry.npmjs.org/redis/-/redis-5.6.1.tgz", + "integrity": "sha512-O9DwAvcBm/lrlkGE0A6gNBtUdA8J9oD9njeLYlLzmm+MGTR7nd7VkpspfXqeXFg3gm89zldDqckyaHhXfhY80g==", + "license": "MIT", + "dependencies": { + "@redis/bloom": "5.6.1", + "@redis/client": "5.6.1", + "@redis/json": "5.6.1", + "@redis/search": "5.6.1", + "@redis/time-series": "5.6.1" + }, + "engines": { + "node": ">= 18" + } + }, "node_modules/resolve": { "version": "1.22.1", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", @@ -2781,6 +3098,78 @@ "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==" }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/sort-asc": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/sort-asc/-/sort-asc-0.1.0.tgz", @@ -2840,6 +3229,78 @@ "node": ">= 0.6" } }, + "node_modules/streamsearch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", + "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/string_decoder/node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/stripe": { + "version": "18.3.0", + "resolved": "https://registry.npmjs.org/stripe/-/stripe-18.3.0.tgz", + "integrity": "sha512-FkxrTUUcWB4CVN2yzgsfF/YHD6WgYHduaa7VmokCy5TLCgl5UNJkwortxcedrxSavQ8Qfa4Ir4JxcbIYiBsyLg==", + "license": "MIT", + "dependencies": { + "qs": "^6.11.0" + }, + "engines": { + "node": ">=12.*" + }, + "peerDependencies": { + "@types/node": ">=12.x.x" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/stripe/node_modules/qs": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", + "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/supports-preserve-symlinks-flag": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", @@ -2940,6 +3401,12 @@ "node": ">= 0.6" } }, + "node_modules/typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==", + "license": "MIT" + }, "node_modules/unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", @@ -2979,6 +3446,12 @@ "browserslist": ">= 4.21.0" } }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "license": "MIT" + }, "node_modules/utils-merge": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", @@ -3060,6 +3533,15 @@ "node": ">=12.0" } }, + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "license": "MIT", + "engines": { + "node": ">=0.4" + } + }, "node_modules/yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", @@ -3318,6 +3800,38 @@ "resolved": "https://registry.npmjs.org/@petamoriken/float16/-/float16-3.6.6.tgz", "integrity": "sha512-3MUulwMtsdCA9lw8a/Kc0XDBJJVCkYTQ5aGd+///TbfkOMXoOGAzzoiYKwPEsLYZv7He7fKJ/mCacqKOO7REyg==" }, + "@redis/bloom": { + "version": "5.6.1", + "resolved": "https://registry.npmjs.org/@redis/bloom/-/bloom-5.6.1.tgz", + "integrity": "sha512-5/22U76IMEfn6TeZ+uvjXspHw+ykBF0kpBa8xouzeHaQMXs/auqBUOEYzU2VKYDvnd2RSpPTyIg82oB7PpUgLg==", + "requires": {} + }, + "@redis/client": { + "version": "5.6.1", + "resolved": "https://registry.npmjs.org/@redis/client/-/client-5.6.1.tgz", + "integrity": "sha512-bWHmSFIJ5w1Y4aHsYs46XMDHKQsBHFRhNcllYaBxz2Zl+lu+gbm5yI9BqxvKh48bLTs/Wx1Kns0gN2WIasE8MA==", + "requires": { + "cluster-key-slot": "1.1.2" + } + }, + "@redis/json": { + "version": "5.6.1", + "resolved": "https://registry.npmjs.org/@redis/json/-/json-5.6.1.tgz", + "integrity": "sha512-cTggVzPIVuiFeXcEcnTRiUzV7rmUvM9KUYxWiHyjsAVACTEUe4ifKkvzrij0H/z3ammU5tfGACffDB3olBwtVA==", + "requires": {} + }, + "@redis/search": { + "version": "5.6.1", + "resolved": "https://registry.npmjs.org/@redis/search/-/search-5.6.1.tgz", + "integrity": "sha512-+eOjx8O2YoKygjqkLpTHqcAq0zKLjior+ee2tRBx/3RSf1+OHxiC9Y6NstshQpvB1XHqTw9n7+f0+MsRJZrp0g==", + "requires": {} + }, + "@redis/time-series": { + "version": "5.6.1", + "resolved": "https://registry.npmjs.org/@redis/time-series/-/time-series-5.6.1.tgz", + "integrity": "sha512-sd3q4jMJdoSO2akw1L9NrdFI1JJ6zeMgMUoTh4a34p9sY3AnOI4aDLCecy8L2IcPAP1oNR3TbLFJiCJDQ35QTA==", + "requires": {} + }, "@tailwindcss/cli": { "version": "4.1.11", "resolved": "https://registry.npmjs.org/@tailwindcss/cli/-/cli-4.1.11.tgz", @@ -3496,6 +4010,11 @@ "negotiator": "0.6.3" } }, + "append-field": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/append-field/-/append-field-1.0.0.tgz", + "integrity": "sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw==" + }, "argparse": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", @@ -3581,6 +4100,19 @@ "update-browserslist-db": "^1.1.3" } }, + "buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==" + }, + "busboy": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", + "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==", + "requires": { + "streamsearch": "^1.1.0" + } + }, "bytes": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", @@ -3595,6 +4127,24 @@ "get-intrinsic": "^1.0.2" } }, + "call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "requires": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + } + }, + "call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "requires": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + } + }, "caniuse-lite": { "version": "1.0.30001727", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001727.tgz", @@ -3606,6 +4156,22 @@ "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==" }, + "cluster-key-slot": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/cluster-key-slot/-/cluster-key-slot-1.1.2.tgz", + "integrity": "sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==" + }, + "concat-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-2.0.0.tgz", + "integrity": "sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==", + "requires": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.0.2", + "typedarray": "^0.0.6" + } + }, "content-disposition": { "version": "0.5.2", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz", @@ -3678,6 +4244,16 @@ "resolved": "https://registry.npmjs.org/dottie/-/dottie-2.0.2.tgz", "integrity": "sha512-fmrwR04lsniq/uSr8yikThDTrM7epXHBAAjH9TbeH3rEA8tdCO7mRzB9hdmdGyJCxF8KERo9CITcm3kGuoyMhg==" }, + "dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "requires": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + } + }, "earcut": { "version": "2.2.4", "resolved": "https://registry.npmjs.org/earcut/-/earcut-2.2.4.tgz", @@ -3708,6 +4284,24 @@ "tapable": "^2.2.0" } }, + "es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==" + }, + "es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==" + }, + "es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "requires": { + "es-errors": "^1.3.0" + } + }, "escalade": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", @@ -3812,9 +4406,9 @@ "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==" }, "function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==" }, "generate-function": { "version": "2.3.1", @@ -3839,15 +4433,36 @@ } }, "get-intrinsic": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.3.tgz", - "integrity": "sha512-QJVz1Tj7MS099PevUG5jvnt9tSkXN8K14dxQlikJuPt4uD9hHAHjLyLBiLR5zELelBdD9QNRAXZzsJx0WaDL9A==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", "requires": { - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.3" + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" } }, + "get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "requires": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + } + }, + "gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==" + }, "graceful-fs": { "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", @@ -3862,9 +4477,9 @@ } }, "has-symbols": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", - "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==" + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==" }, "has-tostringtag": { "version": "1.0.0", @@ -3874,6 +4489,14 @@ "has-symbols": "^1.0.2" } }, + "hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "requires": { + "function-bind": "^1.1.2" + } + }, "http-errors": { "version": "1.6.3", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", @@ -4141,6 +4764,11 @@ } } }, + "math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==" + }, "media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", @@ -4241,6 +4869,30 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" }, + "multer": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/multer/-/multer-2.0.2.tgz", + "integrity": "sha512-u7f2xaZ/UG8oLXHvtF/oWTRvT44p9ecwBBqTwgJVq0+4BW1g8OW01TyMEGWBHbyMOYVHXslaut7qEQ1meATXgw==", + "requires": { + "append-field": "^1.0.0", + "busboy": "^1.6.0", + "concat-stream": "^2.0.0", + "mkdirp": "^0.5.6", + "object-assign": "^4.1.1", + "type-is": "^1.6.18", + "xtend": "^4.0.2" + }, + "dependencies": { + "mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "requires": { + "minimist": "^1.2.6" + } + } + } + }, "mysql2": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/mysql2/-/mysql2-2.3.3.tgz", @@ -4330,6 +4982,11 @@ "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==" }, + "object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==" + }, "ol": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/ol/-/ol-7.1.0.tgz", @@ -4683,6 +5340,28 @@ "quickselect": "^2.0.0" } }, + "readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + }, + "redis": { + "version": "5.6.1", + "resolved": "https://registry.npmjs.org/redis/-/redis-5.6.1.tgz", + "integrity": "sha512-O9DwAvcBm/lrlkGE0A6gNBtUdA8J9oD9njeLYlLzmm+MGTR7nd7VkpspfXqeXFg3gm89zldDqckyaHhXfhY80g==", + "requires": { + "@redis/bloom": "5.6.1", + "@redis/client": "5.6.1", + "@redis/json": "5.6.1", + "@redis/search": "5.6.1", + "@redis/time-series": "5.6.1" + } + }, "resolve": { "version": "1.22.1", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", @@ -4818,6 +5497,50 @@ "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==" }, + "side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "requires": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + } + }, + "side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "requires": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + } + }, + "side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "requires": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + } + }, + "side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "requires": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + } + }, "sort-asc": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/sort-asc/-/sort-asc-0.1.0.tgz", @@ -4857,6 +5580,44 @@ "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz", "integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew==" }, + "streamsearch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", + "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==" + }, + "string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "requires": { + "safe-buffer": "~5.2.0" + }, + "dependencies": { + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" + } + } + }, + "stripe": { + "version": "18.3.0", + "resolved": "https://registry.npmjs.org/stripe/-/stripe-18.3.0.tgz", + "integrity": "sha512-FkxrTUUcWB4CVN2yzgsfF/YHD6WgYHduaa7VmokCy5TLCgl5UNJkwortxcedrxSavQ8Qfa4Ir4JxcbIYiBsyLg==", + "requires": { + "qs": "^6.11.0" + }, + "dependencies": { + "qs": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", + "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==", + "requires": { + "side-channel": "^1.1.0" + } + } + } + }, "supports-preserve-symlinks-flag": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", @@ -4929,6 +5690,11 @@ "mime-types": "~2.1.24" } }, + "typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==" + }, "unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", @@ -4944,6 +5710,11 @@ "picocolors": "^1.1.1" } }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" + }, "utils-merge": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", @@ -5007,6 +5778,11 @@ "js-yaml": "3.14.1" } }, + "xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==" + }, "yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", diff --git a/package.json b/package.json index b8720f0..9321583 100644 --- a/package.json +++ b/package.json @@ -17,11 +17,14 @@ "http-errors": "~1.6.3", "mariadb": "^3.0.1", "morgan": "~1.9.1", + "multer": "^2.0.2", "mysql2": "^2.3.3", "node-fetch": "^2.7.0", "ol": "^7.1.0", "pug": "^3.0.2", + "redis": "^5.6.1", "sequelize": "^6.21.6", + "stripe": "^18.3.0", "xmlbuilder2": "^3.1.1" }, "devDependencies": { diff --git a/public/chat.js b/public/chat.js new file mode 100644 index 0000000..d4a9bb7 --- /dev/null +++ b/public/chat.js @@ -0,0 +1,95 @@ +const messageInput = document.getElementById("message-input"); +const sendBtn = document.getElementById("send-btn"); +const messageList = document.getElementById("message-list"); +const saveBtn = document.getElementById("save-btn"); +const chatMessages = document.getElementById("chat-messages"); + +let lastMessageCount = 0; + +// Send message +async function sendMessage() { + const message = messageInput.value.trim(); + if (!message) return; + + try { + const response = await fetch("/send", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ message }), + }); + + if (response.ok) { + messageInput.value = ""; + await fetchMessages(); + } + } catch (err) { + console.error("Failed to send message:", err); + } +} + +// Fetch all messages +async function fetchMessages() { + try { + const response = await fetch("/chat/all"); + const messages = await response.json(); + + messageList.innerHTML = ""; + messages.forEach((msg) => { + const li = document.createElement("li"); + li.className = "p-2 bg-gray-100 rounded"; + li.innerHTML = ` +
${new Date( + msg.timestamp + ).toLocaleString()}
+
${msg.message}
+ `; + messageList.appendChild(li); + }); + + // Auto-scroll to bottom + chatMessages.scrollTop = chatMessages.scrollHeight; + lastMessageCount = messages.length; + } catch (err) { + console.error("Failed to fetch messages:", err); + } +} + +// Poll for updates +async function pollForUpdates() { + try { + const response = await fetch(`/poll?lastCheck=${lastMessageCount}`); + const data = await response.json(); + + if (data.updated) { + await fetchMessages(); + } + } catch (err) { + console.error("Poll failed:", err); + } +} + +// Save chat +async function saveChat() { + try { + const response = await fetch("/chat/save", { method: "POST" }); + if (response.ok) { + alert("Chat saved successfully!"); + } else { + alert("Failed to save chat"); + } + } catch (err) { + console.error("Failed to save chat:", err); + alert("Failed to save chat"); + } +} + +// Event listeners +sendBtn.addEventListener("click", sendMessage); +messageInput.addEventListener("keypress", (e) => { + if (e.key === "Enter") sendMessage(); +}); +saveBtn.addEventListener("click", saveChat); + +// Initial load and start polling +fetchMessages(); +setInterval(pollForUpdates, 2000); diff --git a/public/flow.js b/public/flow.js new file mode 100644 index 0000000..4a4e9b3 --- /dev/null +++ b/public/flow.js @@ -0,0 +1,227 @@ +// DOM elements +const createFlowBtn = document.getElementById("create-flow-btn"); +const flowNameInput = document.getElementById("flow-name"); +const flowDescriptionInput = document.getElementById("flow-description"); +const flowSelect = document.getElementById("flow-select"); +const actionTypeSelect = document.getElementById("action-type"); +const taskInput = document.getElementById("task-input"); +const orderIndexInput = document.getElementById("order-index"); +const addTaskBtn = document.getElementById("add-task-btn"); +const flowDetails = document.getElementById("flow-details"); +const executeFlowSelect = document.getElementById("execute-flow-select"); +const executePayloadInput = document.getElementById("execute-payload"); +const executeBtn = document.getElementById("execute-btn"); +const webhookBtn = document.getElementById("webhook-btn"); +const executionResults = document.getElementById("execution-results"); + +let currentFlows = []; + +// Create new flow +async function createFlow() { + const name = flowNameInput.value.trim(); + const description = flowDescriptionInput.value.trim(); + + if (!name) { + alert("Flow name is required"); + return; + } + + try { + const response = await fetch("/flow", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ name, description }), + }); + + if (response.ok) { + const flow = await response.json(); + alert(`Flow "${flow.name}" created successfully!`); + flowNameInput.value = ""; + flowDescriptionInput.value = ""; + loadFlows(); + } else { + const error = await response.json(); + alert(`Error: ${error.error}`); + } + } catch (err) { + alert("Failed to create flow"); + } +} + +// Load all flows +async function loadFlows() { + try { + const response = await fetch("/flows"); + const flows = await response.json(); + currentFlows = flows; + + // Update flow selects + flowSelect.innerHTML = ''; + executeFlowSelect.innerHTML = + ''; + + flows.forEach((flow) => { + flowSelect.innerHTML += ``; + executeFlowSelect.innerHTML += ``; + }); + } catch (err) { + console.error("Failed to load flows"); + } +} + +// Add task to flow +async function addTask() { + const flowId = flowSelect.value; + const actionType = actionTypeSelect.value; + const inputData = taskInput.value.trim(); + const orderIndex = parseInt(orderIndexInput.value); + + if (!flowId || !actionType || !inputData || isNaN(orderIndex)) { + alert("All fields are required"); + return; + } + + try { + const response = await fetch(`/flow/${flowId}/task`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + action_type: actionType, + input_data: inputData, + order_index: orderIndex, + }), + }); + + if (response.ok) { + const task = await response.json(); + alert(`Task "${task.action_type}" added successfully!`); + taskInput.value = ""; + orderIndexInput.value = ""; + loadFlowDetails(flowId); + } else { + const error = await response.json(); + alert(`Error: ${error.error}`); + } + } catch (err) { + alert("Failed to add task"); + } +} + +// Load flow details +async function loadFlowDetails(flowId) { + if (!flowId) return; + + try { + const response = await fetch(`/flow/${flowId}`); + const data = await response.json(); + + flowDetails.innerHTML = ` +
+

${data.flow.name}

+

${ + data.flow.description || "No description" + }

+
+

Tasks:

+
+ ${data.tasks + .map( + (task) => ` +
+
${task.action_type}
+
Input: ${task.input_data}
+
Order: ${task.order_index}
+
+ ` + ) + .join("")} +
+
+
+ `; + } catch (err) { + console.error("Failed to load flow details"); + } +} + +// Execute flow +async function executeFlow() { + const flowId = executeFlowSelect.value; + const payload = executePayloadInput.value.trim(); + + if (!flowId) { + alert("Please select a flow to execute"); + return; + } + + try { + const response = await fetch(`/flow/${flowId}/execute`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ payload }), + }); + + if (response.ok) { + const result = await response.json(); + showExecutionResults(result); + } else { + const error = await response.json(); + alert(`Error: ${error.error}`); + } + } catch (err) { + alert("Failed to execute flow"); + } +} + +// Show execution results +function showExecutionResults(result) { + executionResults.innerHTML = ` +

Execution Results:

+
+ ${result.results + .map( + (r) => ` +
+
Task ${r.task_id}
+
${r.result}
+
Status: ${r.status}
+
+ ` + ) + .join("")} +
+ `; + executionResults.classList.remove("hidden"); +} + +// Get webhook URL +function getWebhookUrl() { + const flowId = executeFlowSelect.value; + + if (!flowId) { + alert("Please select a flow"); + return; + } + + const webhookUrl = `${window.location.origin}/flow/${flowId}/trigger?payload=test@example.com`; + alert( + `Webhook URL:\n${webhookUrl}\n\nCopy this URL to trigger the flow via webhook.` + ); +} + +// Event listeners +createFlowBtn.addEventListener("click", createFlow); +addTaskBtn.addEventListener("click", addTask); +executeBtn.addEventListener("click", executeFlow); +webhookBtn.addEventListener("click", getWebhookUrl); + +flowSelect.addEventListener("change", (e) => { + if (e.target.value) { + loadFlowDetails(e.target.value); + } +}); + +// Initialize +loadFlows(); diff --git a/public/main.js b/public/main.js index 2a9b62c..7bdad4d 100644 --- a/public/main.js +++ b/public/main.js @@ -79,68 +79,6 @@ async function updateClocks() { updateClocks(); setInterval(updateClocks, 1000); -// --- Airport Autocomplete --- -// const airportInput = document.querySelector('input[placeholder*="airport"]'); -// let dropdownDiv; -// if (airportInput) { -// console.log("hi"); -// dropdownDiv = document.createElement("div"); -// dropdownDiv.className = -// "absolute bg-white border border-gray-300 rounded shadow z-10 w-full max-h-48 overflow-y-auto top-[calc(100%+10px)]"; -// dropdownDiv.style.display = "none"; -// airportInput.parentNode.appendChild(dropdownDiv); -// airportInput.addEventListener("input", async function () { -// const val = airportInput.value.trim(); -// if (val.length < 3) { -// dropdownDiv.style.display = "none"; -// return; -// } -// try { -// const res = await fetch(`/airports?search=${encodeURIComponent(val)}`); -// if (!res.ok) throw new Error("Failed to fetch airports"); -// const airports = await res.json(); -// if (!Array.isArray(airports) || airports.length === 0) { -// dropdownDiv.innerHTML = -// '
No results
'; -// dropdownDiv.style.display = "block"; -// return; -// } -// dropdownDiv.innerHTML = airports -// .map( -// (a, i) => -// `
${a.name}
` -// ) -// .join(""); -// dropdownDiv.style.display = "block"; -// Array.from(dropdownDiv.children).forEach((child, i) => { -// child.addEventListener("click", () => { -// airportInput.value = child.textContent; -// dropdownDiv.style.display = "none"; -// selectedAirport = airports[i]; -// console.log("hey"); -// airportInput.textContent = `hold`; -// if (selectedAirport) { -// console.log(selectedAirport); -// airportInput.value = `${selectedAirport.name} (${selectedAirport.code})`; -// showMap( -// Number(selectedAirport.latitude_deg), -// Number(selectedAirport.longitude_deg) -// ); -// } -// }); -// }); -// } catch (err) { -// dropdownDiv.innerHTML = -// '
Error loading airports
'; -// dropdownDiv.style.display = "block"; -// } -// }); -// // Hide dropdown on blur -// airportInput.addEventListener("blur", () => -// setTimeout(() => (dropdownDiv.style.display = "none"), 200) -// ); -// } - // --- Airport Autocomplete --- const airportInput = document.querySelector('input[placeholder*="airport"]'); let dropdownDiv; @@ -163,10 +101,6 @@ if (airportInput) { selectedAirport = currentAirports[index]; // Use latest fetched data airportInput.value = `${selectedAirport.name} (${selectedAirport.code})`; // Set once - // showMap( - // Number(selectedAirport.latitude_deg), - // Number(selectedAirport.longitude_deg) - // ); onAirportSelected(selectedAirport); dropdownDiv.style.display = "none"; }); @@ -307,6 +241,11 @@ function logWidgetClick(widgetName) { widget_name: widgetName, browser_type: navigator.userAgent, }), + }).then(async (res) => { + if (res.status === 429) { + const data = await res.json(); + if (data.redirect) window.location.href = data.redirect; + } }); } @@ -394,6 +333,55 @@ if (coinInput && coinBtn && coinResult) { }); } +// --- Upload Widget --- +const uploadInput = document.querySelector( + 'input[type="file"][id="upload-input"]' +); +const uploadBtn = document.getElementById("upload-btn"); +const uploadPreview = document.getElementById("upload-preview"); +const uploadError = document.getElementById("upload-error"); + +async function fetchLatestUpload() { + try { + const res = await fetch("/upload/latest"); + const data = await res.json(); + if (data.url && uploadPreview) { + uploadPreview.src = data.url; + uploadPreview.style.display = ""; + } else if (uploadPreview) { + uploadPreview.style.display = "none"; + } + } catch {} +} + +if (uploadBtn && uploadInput) { + uploadBtn.addEventListener("click", async function () { + if (!uploadInput.files || !uploadInput.files[0]) { + if (uploadError) uploadError.textContent = "Please select an image."; + return; + } + const formData = new FormData(); + formData.append("image", uploadInput.files[0]); + try { + const res = await fetch("/upload", { method: "POST", body: formData }); + const data = await res.json(); + if (data.url) { + if (uploadPreview) { + uploadPreview.src = data.url; + uploadPreview.style.display = ""; + } + if (uploadError) uploadError.textContent = ""; + } else { + if (uploadError) + uploadError.textContent = data.error || "Upload failed."; + } + } catch (err) { + if (uploadError) uploadError.textContent = "Upload failed."; + } + }); +} +fetchLatestUpload(); + // Attach click listeners to widgets function attachAnalyticListeners() { const widgets = [ diff --git a/public/stylesheets/output.css b/public/stylesheets/output.css index 827e647..ca328ae 100644 --- a/public/stylesheets/output.css +++ b/public/stylesheets/output.css @@ -8,14 +8,20 @@ --font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; --color-red-500: oklch(63.7% 0.237 25.331); + --color-green-500: oklch(72.3% 0.219 149.579); + --color-green-600: oklch(62.7% 0.194 149.214); --color-sky-100: oklch(95.1% 0.026 236.824); --color-sky-400: oklch(74.6% 0.16 232.661); - --color-blue-100: oklch(93.2% 0.032 255.585); + --color-blue-500: oklch(62.3% 0.214 259.815); + --color-blue-600: oklch(54.6% 0.245 262.881); + --color-blue-700: oklch(48.8% 0.243 264.376); --color-gray-100: oklch(96.7% 0.003 264.542); --color-gray-300: oklch(87.2% 0.01 258.338); --color-gray-500: oklch(55.1% 0.027 264.364); + --color-gray-600: oklch(44.6% 0.03 256.802); --color-white: #fff; --spacing: 0.25rem; + --container-2xl: 42rem; --text-xs: 0.75rem; --text-xs--line-height: calc(1 / 0.75); --text-sm: 0.875rem; @@ -198,6 +204,27 @@ .row-span-2 { grid-row: span 2 / span 2; } + .container { + width: 100%; + @media (width >= 40rem) { + max-width: 40rem; + } + @media (width >= 48rem) { + max-width: 48rem; + } + @media (width >= 64rem) { + max-width: 64rem; + } + @media (width >= 80rem) { + max-width: 80rem; + } + @media (width >= 96rem) { + max-width: 96rem; + } + } + .mx-auto { + margin-inline: auto; + } .mt-2 { margin-top: calc(var(--spacing) * 2); } @@ -210,6 +237,12 @@ .mb-2 { margin-bottom: calc(var(--spacing) * 2); } + .mb-4 { + margin-bottom: calc(var(--spacing) * 4); + } + .mb-6 { + margin-bottom: calc(var(--spacing) * 6); + } .ml-2 { margin-left: calc(var(--spacing) * 2); } @@ -228,6 +261,9 @@ .h-24 { height: calc(var(--spacing) * 24); } + .h-96 { + height: calc(var(--spacing) * 96); + } .h-244 { height: calc(var(--spacing) * 244); } @@ -252,6 +288,12 @@ .w-full { width: 100%; } + .max-w-2xl { + max-width: var(--container-2xl); + } + .flex-1 { + flex: 1; + } .cursor-pointer { cursor: pointer; } @@ -276,6 +318,9 @@ .justify-center { justify-content: center; } + .gap-2 { + gap: calc(var(--spacing) * 2); + } .gap-8 { gap: calc(var(--spacing) * 8); } @@ -302,12 +347,15 @@ .border-gray-300 { border-color: var(--color-gray-300); } - .bg-blue-100 { - background-color: var(--color-blue-100); + .bg-blue-500 { + background-color: var(--color-blue-500); } .bg-gray-100 { background-color: var(--color-gray-100); } + .bg-green-500 { + background-color: var(--color-green-500); + } .bg-inherit { background-color: inherit; } @@ -317,6 +365,9 @@ .bg-white { background-color: var(--color-white); } + .object-cover { + object-fit: cover; + } .p-2 { padding: calc(var(--spacing) * 2); } @@ -329,6 +380,12 @@ .p-8 { padding: calc(var(--spacing) * 8); } + .px-3 { + padding-inline: calc(var(--spacing) * 3); + } + .px-6 { + padding-inline: calc(var(--spacing) * 6); + } .px-8 { padding-inline: calc(var(--spacing) * 8); } @@ -373,9 +430,15 @@ --tw-font-weight: var(--font-weight-semibold); font-weight: var(--font-weight-semibold); } + .text-blue-700 { + color: var(--color-blue-700); + } .text-gray-500 { color: var(--color-gray-500); } + .text-gray-600 { + color: var(--color-gray-600); + } .text-red-500 { color: var(--color-red-500); } @@ -390,14 +453,24 @@ --tw-shadow: 0 4px 6px -1px var(--tw-shadow-color, rgb(0 0 0 / 0.1)), 0 2px 4px -2px var(--tw-shadow-color, rgb(0 0 0 / 0.1)); box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow); } - .blur { - --tw-blur: blur(8px); - filter: var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,); - } .outline-none { --tw-outline-style: none; outline-style: none; } + .hover\:bg-blue-600 { + &:hover { + @media (hover: hover) { + background-color: var(--color-blue-600); + } + } + } + .hover\:bg-green-600 { + &:hover { + @media (hover: hover) { + background-color: var(--color-green-600); + } + } + } .hover\:bg-sky-100 { &:hover { @media (hover: hover) { @@ -405,6 +478,30 @@ } } } + .hover\:underline { + &:hover { + @media (hover: hover) { + text-decoration-line: underline; + } + } + } + .focus\:ring-2 { + &:focus { + --tw-ring-shadow: var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color, currentcolor); + box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow); + } + } + .focus\:ring-blue-500 { + &:focus { + --tw-ring-color: var(--color-blue-500); + } + } + .focus\:outline-none { + &:focus { + --tw-outline-style: none; + outline-style: none; + } + } } @property --tw-space-y-reverse { syntax: "*"; @@ -485,59 +582,6 @@ inherits: false; initial-value: 0 0 #0000; } -@property --tw-blur { - syntax: "*"; - inherits: false; -} -@property --tw-brightness { - syntax: "*"; - inherits: false; -} -@property --tw-contrast { - syntax: "*"; - inherits: false; -} -@property --tw-grayscale { - syntax: "*"; - inherits: false; -} -@property --tw-hue-rotate { - syntax: "*"; - inherits: false; -} -@property --tw-invert { - syntax: "*"; - inherits: false; -} -@property --tw-opacity { - syntax: "*"; - inherits: false; -} -@property --tw-saturate { - syntax: "*"; - inherits: false; -} -@property --tw-sepia { - syntax: "*"; - inherits: false; -} -@property --tw-drop-shadow { - syntax: "*"; - inherits: false; -} -@property --tw-drop-shadow-color { - syntax: "*"; - inherits: false; -} -@property --tw-drop-shadow-alpha { - syntax: ""; - inherits: false; - initial-value: 100%; -} -@property --tw-drop-shadow-size { - syntax: "*"; - inherits: false; -} @layer properties { @supports ((-webkit-hyphens: none) and (not (margin-trim: inline))) or ((-moz-orient: inline) and (not (color:rgb(from red r g b)))) { *, ::before, ::after, ::backdrop { @@ -558,19 +602,6 @@ --tw-ring-offset-width: 0px; --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; - --tw-blur: initial; - --tw-brightness: initial; - --tw-contrast: initial; - --tw-grayscale: initial; - --tw-hue-rotate: initial; - --tw-invert: initial; - --tw-opacity: initial; - --tw-saturate: initial; - --tw-sepia: initial; - --tw-drop-shadow: initial; - --tw-drop-shadow-color: initial; - --tw-drop-shadow-alpha: 100%; - --tw-drop-shadow-size: initial; } } } diff --git a/public/uploads/1753553369314-133902755-c0bbee1b8d96b0b710ce6c09a069f6f2.gif b/public/uploads/1753553369314-133902755-c0bbee1b8d96b0b710ce6c09a069f6f2.gif new file mode 100644 index 0000000..8cdcab8 Binary files /dev/null and b/public/uploads/1753553369314-133902755-c0bbee1b8d96b0b710ce6c09a069f6f2.gif differ diff --git a/routes/index.js b/routes/index.js index 504801a..dbbdd6a 100644 --- a/routes/index.js +++ b/routes/index.js @@ -8,6 +8,25 @@ const airportData = JSON.parse( ); const db = require("../models"); const { create } = require("xmlbuilder2"); +const rateLimitMap = new Map(); +const multer = require("multer"); +const uploadDir = path.join(__dirname, "../public/uploads"); +if (!fs.existsSync(uploadDir)) fs.mkdirSync(uploadDir, { recursive: true }); +const storage = multer.diskStorage({ + destination: function (req, file, cb) { + cb(null, uploadDir); + }, + filename: function (req, file, cb) { + const unique = Date.now() + "-" + Math.round(Math.random() * 1e9); + cb(null, unique + "-" + file.originalname); + }, +}); +const uploadMulter = multer({ storage }); + +const stripe = require("stripe")(process.env.STRIPE_SECRET_KEY); +const redis = require("redis"); +const client = redis.createClient(); +client.connect().catch(console.error); /* GET home page. */ router.get("/", function (req, res, next) { @@ -60,9 +79,20 @@ router.get("/airports", function (req, res) { res.json(matches); }); -// Log analytic event +// Log analytic event with rate limiting router.post("/analytic", async function (req, res) { try { + const ip = req.headers["x-forwarded-for"] || req.connection.remoteAddress; + const now = Date.now(); + const windowMs = 60 * 1000; + const maxReq = 10; + if (!rateLimitMap.has(ip)) rateLimitMap.set(ip, []); + let timestamps = rateLimitMap.get(ip).filter((ts) => now - ts < windowMs); + if (timestamps.length >= maxReq) { + return res.status(429).json({ redirect: "/pay" }); + } + timestamps.push(now); + rateLimitMap.set(ip, timestamps); const { widget_name, browser_type } = req.body; if (!widget_name || !browser_type) { return res @@ -162,4 +192,296 @@ router.post("/coin-calc", function (req, res) { } }); +// Stripe payment page +router.get("/pay", async function (req, res) { + const session = await stripe.checkout.sessions.create({ + payment_method_types: ["card"], + line_items: [ + { + price_data: { + currency: "usd", + product_data: { name: "Widget Analytics Access" }, + unit_amount: 500, + }, + quantity: 1, + }, + ], + mode: "payment", + success_url: req.protocol + "://" + req.get("host") + "/?paid=1", + cancel_url: req.protocol + "://" + req.get("host") + "/pay?cancel=1", + }); + res.send(` + + Pay for Analytics + +

Rate limit exceeded

+

You need to pay $5 to continue using analytics.

+ + + + + + `); +}); + +// Upload image +router.post("/upload", uploadMulter.single("image"), async function (req, res) { + try { + if (!req.file) return res.status(400).json({ error: "No file uploaded" }); + const file = req.file; + const dbUpload = await db.upload.create({ + filename: file.filename, + mimetype: file.mimetype, + path: "/uploads/" + file.filename, + }); + res.json({ url: "/uploads/" + file.filename }); + } catch (err) { + res.status(500).json({ error: "Failed to upload image" }); + } +}); + +// Get latest uploaded image +router.get("/upload/latest", async function (req, res) { + try { + const latest = await db.upload.findOne({ order: [["created_at", "DESC"]] }); + if (!latest) return res.json({ url: null }); + res.json({ url: latest.path }); + } catch (err) { + res.status(500).json({ error: "Failed to fetch latest upload" }); + } +}); + +// Chat routes +router.post("/send", async function (req, res) { + try { + const { message } = req.body; + if (!message) return res.status(400).json({ error: "Message required" }); + + const chatMessage = { + message, + timestamp: new Date().toISOString(), + id: Date.now(), + }; + + await client.lPush("chatroom", JSON.stringify(chatMessage)); + res.json({ success: true }); + } catch (err) { + res.status(500).json({ error: "Failed to send message" }); + } +}); + +router.get("/chat/all", async function (req, res) { + try { + const messages = await client.lRange("chatroom", 0, -1); + const parsedMessages = messages.map((msg) => JSON.parse(msg)); + res.json(parsedMessages); + } catch (err) { + res.status(500).json({ error: "Failed to fetch messages" }); + } +}); + +router.get("/poll", async function (req, res) { + try { + const messageCount = await client.lLen("chatroom"); + const lastCheck = req.query.lastCheck || 0; + + if (messageCount > lastCheck) { + res.json({ updated: true, count: messageCount }); + } else { + res.json({ updated: false, count: messageCount }); + } + } catch (err) { + res.status(500).json({ error: "Poll failed" }); + } +}); + +router.post("/chat/save", async function (req, res) { + try { + const messages = await client.lRange("chatroom", 0, -1); + const chatMessages = JSON.stringify(messages.map((msg) => JSON.parse(msg))); + + await db.chat.create({ + chat_messages: chatMessages, + }); + + res.json({ success: true }); + } catch (err) { + res.status(500).json({ error: "Failed to save chat" }); + } +}); + +// Chat page +router.get("/chat", function (req, res) { + res.sendFile(path.join(__dirname, "../views/chat.html")); +}); + +// Flow Builder routes +router.get("/flow", function (req, res) { + res.sendFile(path.join(__dirname, "../views/flow.html")); +}); + +// Get all flows +router.get("/flows", async function (req, res) { + try { + const flows = await db.flow.findAll({ + order: [["created_at", "DESC"]], + }); + res.json(flows); + } catch (err) { + res.status(500).json({ error: "Failed to fetch flows" }); + } +}); + +// Create new flow +router.post("/flow", async function (req, res) { + try { + const { name, description } = req.body; + if (!name) return res.status(400).json({ error: "Flow name required" }); + + const flow = await db.flow.create({ name, description }); + res.json({ id: flow.id, name: flow.name }); + } catch (err) { + res.status(500).json({ error: "Failed to create flow" }); + } +}); + +// Add task to flow +router.post("/flow/:id/task", async function (req, res) { + try { + const flowId = req.params.id; + const { action_type, input_data, order_index } = req.body; + + if (!action_type || !input_data || order_index === undefined) { + return res + .status(400) + .json({ error: "action_type, input_data, and order_index required" }); + } + + const task = await db.task.create({ + flow_id: flowId, + action_type, + input_data, + order_index, + }); + + res.json({ id: task.id, action_type: task.action_type }); + } catch (err) { + res.status(500).json({ error: "Failed to add task" }); + } +}); + +// Get flow details +router.get("/flow/:id", async function (req, res) { + try { + const flowId = req.params.id; + const flow = await db.flow.findByPk(flowId); + const tasks = await db.task.findAll({ + where: { flow_id: flowId }, + order: [["order_index", "ASC"]], + }); + + res.json({ flow, tasks }); + } catch (err) { + res.status(500).json({ error: "Failed to fetch flow" }); + } +}); + +// Execute flow +router.post("/flow/:id/execute", async function (req, res) { + try { + const flowId = req.params.id; + const { payload } = req.body; + + const flow = await db.flow.findByPk(flowId); + const tasks = await db.task.findAll({ + where: { flow_id: flowId }, + order: [["order_index", "ASC"]], + }); + + const results = []; + + for (const task of tasks) { + let result = ""; + let status = "success"; + + try { + switch (task.action_type) { + case "send_test_mail": + // Simulate sending email + result = `Email sent to: ${task.input_data}`; + break; + + case "http_get_request": + const response = await fetch(task.input_data); + result = `HTTP GET ${task.input_data}: ${response.status}`; + break; + + case "mysql_select": + const [table, id] = task.input_data.split("|"); + const query = `SELECT * FROM ${table} WHERE id = ${id}`; + result = `Query executed: ${query}`; + break; + + case "drive_upload": + result = `File uploaded to Google Drive with content: ${task.input_data}`; + break; + + default: + result = `Unknown action type: ${task.action_type}`; + status = "error"; + } + } catch (err) { + result = `Error: ${err.message}`; + status = "error"; + } + + // Log the execution + await db.flow_log.create({ + flow_id: flowId, + task_id: task.id, + result, + status, + }); + + results.push({ task_id: task.id, result, status }); + } + + res.json({ flow_id: flowId, results }); + } catch (err) { + res.status(500).json({ error: "Failed to execute flow" }); + } +}); + +// Webhook trigger +router.get("/flow/:id/trigger", async function (req, res) { + try { + const flowId = req.params.id; + const payload = req.query.payload; + + if (!payload) { + return res.status(400).json({ error: "Payload required" }); + } + + // Execute the flow with the payload + const response = await fetch( + `${req.protocol}://${req.get("host")}/flow/${flowId}/execute`, + { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ payload }), + } + ); + + const result = await response.json(); + res.json(result); + } catch (err) { + res.status(500).json({ error: "Failed to trigger flow" }); + } +}); + module.exports = router; diff --git a/views/chat.html b/views/chat.html new file mode 100644 index 0000000..e2429ef --- /dev/null +++ b/views/chat.html @@ -0,0 +1,49 @@ + + + + + + Chat Room + + + +
+

Chat Room

+ +
+
+
    + +
+
+ +
+ + +
+ + +
+
+ + + + diff --git a/views/flow.html b/views/flow.html new file mode 100644 index 0000000..f133d73 --- /dev/null +++ b/views/flow.html @@ -0,0 +1,127 @@ + + + + + + Flow Builder + + + +
+

Flow Builder

+ + +
+

Create New Flow

+
+ + +
+ +
+ + +
+

Add Task to Flow

+
+ + + + +
+ +
+ + +
+

Flow Details

+
+ +
+
+ + +
+

Execute Flow

+
+ + +
+
+ + +
+ +
+
+ + + + diff --git a/views/index.html b/views/index.html index e12c04e..f5446b2 100644 --- a/views/index.html +++ b/views/index.html @@ -137,14 +137,21 @@ https://cdn.jsdelivr.net/npm/tailwindcss@4.1.11/dist/lib.min.js
- - +