From 223add151c48e6dec91b30612b271bef2ae09bfe Mon Sep 17 00:00:00 2001 From: qwerinope Date: Wed, 17 Sep 2025 17:51:22 +0200 Subject: [PATCH] moving to postgres, part 1 --- bun.lock | 158 ++++++++++++++++++++++++++++++++++- compose.yml | 28 +++++-- drizzle.config.ts | 9 ++ package.json | 8 +- src/commands/getloot.ts | 18 ++-- src/connectionCheck.ts | 2 - src/db/connection.ts | 89 ++++---------------- src/db/dbAnivTimeouts.ts | 21 +++-- src/db/dbAuth.ts | 40 ++++----- src/db/dbCheerEvents.ts | 32 +++---- src/db/dbCheers.ts | 29 +++---- src/db/dbGetLoot.ts | 20 ++--- src/db/dbTimeouts.ts | 52 ++++++------ src/db/dbUsedItems.ts | 33 +++----- src/db/dbUser.ts | 77 ++++++++--------- src/db/schema.ts | 64 ++++++++++++++ src/items/index.ts | 23 ++--- src/items/silverbullet.ts | 2 +- src/lib/handleAnivMessage.ts | 10 ++- 19 files changed, 425 insertions(+), 290 deletions(-) create mode 100644 drizzle.config.ts create mode 100644 src/db/schema.ts diff --git a/bun.lock b/bun.lock index 8a404d6..c170114 100644 --- a/bun.lock +++ b/bun.lock @@ -7,11 +7,13 @@ "@fontsource/jersey-15": "^5.2.6", "@twurple/auth": "^7.3.0", "@twurple/eventsub-ws": "^7.3.0", + "drizzle-orm": "^0.44.5", "kleur": "^4.1.5", - "pocketbase": "^0.26.1", }, "devDependencies": { "@types/bun": "latest", + "drizzle-kit": "^0.31.4", + "pg": "^8.16.3", }, "peerDependencies": { "typescript": "^5.8.3", @@ -39,6 +41,64 @@ "@d-fischer/typed-event-emitter": ["@d-fischer/typed-event-emitter@3.3.3", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-OvSEOa8icfdWDqcRtjSEZtgJTFOFNgTjje7zaL0+nAtu2/kZtRCSK5wUMrI/aXtCH8o0Qz2vA8UqkhWUTARFQQ=="], + "@drizzle-team/brocli": ["@drizzle-team/brocli@0.10.2", "", {}, "sha512-z33Il7l5dKjUgGULTqBsQBQwckHh5AbIuxhdsIxDDiZAzBOrZO6q9ogcWC65kU382AfynTfgNumVcNIjuIua6w=="], + + "@esbuild-kit/core-utils": ["@esbuild-kit/core-utils@3.3.2", "", { "dependencies": { "esbuild": "~0.18.20", "source-map-support": "^0.5.21" } }, "sha512-sPRAnw9CdSsRmEtnsl2WXWdyquogVpB3yZ3dgwJfe8zrOzTsV7cJvmwrKVa+0ma5BoiGJ+BoqkMvawbayKUsqQ=="], + + "@esbuild-kit/esm-loader": ["@esbuild-kit/esm-loader@2.6.5", "", { "dependencies": { "@esbuild-kit/core-utils": "^3.3.2", "get-tsconfig": "^4.7.0" } }, "sha512-FxEMIkJKnodyA1OaCUoEvbYRkoZlLZ4d/eXFu9Fh8CbBBgP5EmZxrfTRyN0qpXZ4vOvqnE5YdRdcrmUUXuU+dA=="], + + "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.9", "", { "os": "aix", "cpu": "ppc64" }, "sha512-OaGtL73Jck6pBKjNIe24BnFE6agGl+6KxDtTfHhy1HmhthfKouEcOhqpSL64K4/0WCtbKFLOdzD/44cJ4k9opA=="], + + "@esbuild/android-arm": ["@esbuild/android-arm@0.25.9", "", { "os": "android", "cpu": "arm" }, "sha512-5WNI1DaMtxQ7t7B6xa572XMXpHAaI/9Hnhk8lcxF4zVN4xstUgTlvuGDorBguKEnZO70qwEcLpfifMLoxiPqHQ=="], + + "@esbuild/android-arm64": ["@esbuild/android-arm64@0.25.9", "", { "os": "android", "cpu": "arm64" }, "sha512-IDrddSmpSv51ftWslJMvl3Q2ZT98fUSL2/rlUXuVqRXHCs5EUF1/f+jbjF5+NG9UffUDMCiTyh8iec7u8RlTLg=="], + + "@esbuild/android-x64": ["@esbuild/android-x64@0.25.9", "", { "os": "android", "cpu": "x64" }, "sha512-I853iMZ1hWZdNllhVZKm34f4wErd4lMyeV7BLzEExGEIZYsOzqDWDf+y082izYUE8gtJnYHdeDpN/6tUdwvfiw=="], + + "@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.25.9", "", { "os": "darwin", "cpu": "arm64" }, "sha512-XIpIDMAjOELi/9PB30vEbVMs3GV1v2zkkPnuyRRURbhqjyzIINwj+nbQATh4H9GxUgH1kFsEyQMxwiLFKUS6Rg=="], + + "@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.25.9", "", { "os": "darwin", "cpu": "x64" }, "sha512-jhHfBzjYTA1IQu8VyrjCX4ApJDnH+ez+IYVEoJHeqJm9VhG9Dh2BYaJritkYK3vMaXrf7Ogr/0MQ8/MeIefsPQ=="], + + "@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.25.9", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-z93DmbnY6fX9+KdD4Ue/H6sYs+bhFQJNCPZsi4XWJoYblUqT06MQUdBCpcSfuiN72AbqeBFu5LVQTjfXDE2A6Q=="], + + "@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.25.9", "", { "os": "freebsd", "cpu": "x64" }, "sha512-mrKX6H/vOyo5v71YfXWJxLVxgy1kyt1MQaD8wZJgJfG4gq4DpQGpgTB74e5yBeQdyMTbgxp0YtNj7NuHN0PoZg=="], + + "@esbuild/linux-arm": ["@esbuild/linux-arm@0.25.9", "", { "os": "linux", "cpu": "arm" }, "sha512-HBU2Xv78SMgaydBmdor38lg8YDnFKSARg1Q6AT0/y2ezUAKiZvc211RDFHlEZRFNRVhcMamiToo7bDx3VEOYQw=="], + + "@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.25.9", "", { "os": "linux", "cpu": "arm64" }, "sha512-BlB7bIcLT3G26urh5Dmse7fiLmLXnRlopw4s8DalgZ8ef79Jj4aUcYbk90g8iCa2467HX8SAIidbL7gsqXHdRw=="], + + "@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.25.9", "", { "os": "linux", "cpu": "ia32" }, "sha512-e7S3MOJPZGp2QW6AK6+Ly81rC7oOSerQ+P8L0ta4FhVi+/j/v2yZzx5CqqDaWjtPFfYz21Vi1S0auHrap3Ma3A=="], + + "@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.25.9", "", { "os": "linux", "cpu": "none" }, "sha512-Sbe10Bnn0oUAB2AalYztvGcK+o6YFFA/9829PhOCUS9vkJElXGdphz0A3DbMdP8gmKkqPmPcMJmJOrI3VYB1JQ=="], + + "@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.25.9", "", { "os": "linux", "cpu": "none" }, "sha512-YcM5br0mVyZw2jcQeLIkhWtKPeVfAerES5PvOzaDxVtIyZ2NUBZKNLjC5z3/fUlDgT6w89VsxP2qzNipOaaDyA=="], + + "@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.25.9", "", { "os": "linux", "cpu": "ppc64" }, "sha512-++0HQvasdo20JytyDpFvQtNrEsAgNG2CY1CLMwGXfFTKGBGQT3bOeLSYE2l1fYdvML5KUuwn9Z8L1EWe2tzs1w=="], + + "@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.25.9", "", { "os": "linux", "cpu": "none" }, "sha512-uNIBa279Y3fkjV+2cUjx36xkx7eSjb8IvnL01eXUKXez/CBHNRw5ekCGMPM0BcmqBxBcdgUWuUXmVWwm4CH9kg=="], + + "@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.25.9", "", { "os": "linux", "cpu": "s390x" }, "sha512-Mfiphvp3MjC/lctb+7D287Xw1DGzqJPb/J2aHHcHxflUo+8tmN/6d4k6I2yFR7BVo5/g7x2Monq4+Yew0EHRIA=="], + + "@esbuild/linux-x64": ["@esbuild/linux-x64@0.25.9", "", { "os": "linux", "cpu": "x64" }, "sha512-iSwByxzRe48YVkmpbgoxVzn76BXjlYFXC7NvLYq+b+kDjyyk30J0JY47DIn8z1MO3K0oSl9fZoRmZPQI4Hklzg=="], + + "@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.25.9", "", { "os": "none", "cpu": "arm64" }, "sha512-9jNJl6FqaUG+COdQMjSCGW4QiMHH88xWbvZ+kRVblZsWrkXlABuGdFJ1E9L7HK+T0Yqd4akKNa/lO0+jDxQD4Q=="], + + "@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.25.9", "", { "os": "none", "cpu": "x64" }, "sha512-RLLdkflmqRG8KanPGOU7Rpg829ZHu8nFy5Pqdi9U01VYtG9Y0zOG6Vr2z4/S+/3zIyOxiK6cCeYNWOFR9QP87g=="], + + "@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.25.9", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-YaFBlPGeDasft5IIM+CQAhJAqS3St3nJzDEgsgFixcfZeyGPCd6eJBWzke5piZuZ7CtL656eOSYKk4Ls2C0FRQ=="], + + "@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.25.9", "", { "os": "openbsd", "cpu": "x64" }, "sha512-1MkgTCuvMGWuqVtAvkpkXFmtL8XhWy+j4jaSO2wxfJtilVCi0ZE37b8uOdMItIHz4I6z1bWWtEX4CJwcKYLcuA=="], + + "@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.25.9", "", { "os": "none", "cpu": "arm64" }, "sha512-4Xd0xNiMVXKh6Fa7HEJQbrpP3m3DDn43jKxMjxLLRjWnRsfxjORYJlXPO4JNcXtOyfajXorRKY9NkOpTHptErg=="], + + "@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.25.9", "", { "os": "sunos", "cpu": "x64" }, "sha512-WjH4s6hzo00nNezhp3wFIAfmGZ8U7KtrJNlFMRKxiI9mxEK1scOMAaa9i4crUtu+tBr+0IN6JCuAcSBJZfnphw=="], + + "@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.25.9", "", { "os": "win32", "cpu": "arm64" }, "sha512-mGFrVJHmZiRqmP8xFOc6b84/7xa5y5YvR1x8djzXpJBSv/UsNK6aqec+6JDjConTgvvQefdGhFDAs2DLAds6gQ=="], + + "@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.25.9", "", { "os": "win32", "cpu": "ia32" }, "sha512-b33gLVU2k11nVx1OhX3C8QQP6UHQK4ZtN56oFWvVXvz2VkDoe6fbG8TOgHFxEvqeqohmRnIHe5A1+HADk4OQww=="], + + "@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.9", "", { "os": "win32", "cpu": "x64" }, "sha512-PPOl1mi6lpLNQxnGoyAfschAodRFYXJ+9fs6WHXz7CSWKbOqiMZsubC+BQsVKuul+3vKLuwTHsS2c2y9EoKwxQ=="], + "@fontsource/jersey-15": ["@fontsource/jersey-15@5.2.6", "", {}, "sha512-3zkkEnu91esusWLqAK/AN1uc6jNtWT8idfO0UfYLqNlbMBKkbbiIVXtq6UbQsyegxnmRMppVV1J2t1zrJ36VgA=="], "@twurple/api": ["@twurple/api@7.3.0", "", { "dependencies": { "@d-fischer/cache-decorators": "^4.0.0", "@d-fischer/cross-fetch": "^5.0.1", "@d-fischer/detect-node": "^3.0.1", "@d-fischer/logger": "^4.2.1", "@d-fischer/rate-limiter": "^1.1.0", "@d-fischer/shared-utils": "^3.6.1", "@d-fischer/typed-event-emitter": "^3.3.1", "@twurple/api-call": "7.3.0", "@twurple/common": "7.3.0", "retry": "^0.13.1", "tslib": "^2.0.3" }, "peerDependencies": { "@twurple/auth": "7.3.0" } }, "sha512-QtaVgYi50E3AB/Nxivjou/u6w1cuQ6g4R8lzQawYDaQNtlP2Ue8vvYuSp2PfxSpe8vNiKhgV8hZAs+j4V29sxQ=="], @@ -59,18 +119,64 @@ "@types/ws": ["@types/ws@8.18.1", "", { "dependencies": { "@types/node": "*" } }, "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg=="], + "buffer-from": ["buffer-from@1.1.2", "", {}, "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="], + "bun-types": ["bun-types@1.2.17", "", { "dependencies": { "@types/node": "*" } }, "sha512-ElC7ItwT3SCQwYZDYoAH+q6KT4Fxjl8DtZ6qDulUFBmXA8YB4xo+l54J9ZJN+k2pphfn9vk7kfubeSd5QfTVJQ=="], + "debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], + + "drizzle-kit": ["drizzle-kit@0.31.4", "", { "dependencies": { "@drizzle-team/brocli": "^0.10.2", "@esbuild-kit/esm-loader": "^2.5.5", "esbuild": "^0.25.4", "esbuild-register": "^3.5.0" }, "bin": { "drizzle-kit": "bin.cjs" } }, "sha512-tCPWVZWZqWVx2XUsVpJRnH9Mx0ClVOf5YUHerZ5so1OKSlqww4zy1R5ksEdGRcO3tM3zj0PYN6V48TbQCL1RfA=="], + + "drizzle-orm": ["drizzle-orm@0.44.5", "", { "peerDependencies": { "@aws-sdk/client-rds-data": ">=3", "@cloudflare/workers-types": ">=4", "@electric-sql/pglite": ">=0.2.0", "@libsql/client": ">=0.10.0", "@libsql/client-wasm": ">=0.10.0", "@neondatabase/serverless": ">=0.10.0", "@op-engineering/op-sqlite": ">=2", "@opentelemetry/api": "^1.4.1", "@planetscale/database": ">=1.13", "@prisma/client": "*", "@tidbcloud/serverless": "*", "@types/better-sqlite3": "*", "@types/pg": "*", "@types/sql.js": "*", "@upstash/redis": ">=1.34.7", "@vercel/postgres": ">=0.8.0", "@xata.io/client": "*", "better-sqlite3": ">=7", "bun-types": "*", "expo-sqlite": ">=14.0.0", "gel": ">=2", "knex": "*", "kysely": "*", "mysql2": ">=2", "pg": ">=8", "postgres": ">=3", "sql.js": ">=1", "sqlite3": ">=5" }, "optionalPeers": ["@aws-sdk/client-rds-data", "@cloudflare/workers-types", "@electric-sql/pglite", "@libsql/client", "@libsql/client-wasm", "@neondatabase/serverless", "@op-engineering/op-sqlite", "@opentelemetry/api", "@planetscale/database", "@prisma/client", "@tidbcloud/serverless", "@types/better-sqlite3", "@types/pg", "@types/sql.js", "@upstash/redis", "@vercel/postgres", "@xata.io/client", "better-sqlite3", "bun-types", "expo-sqlite", "gel", "knex", "kysely", "mysql2", "pg", "postgres", "sql.js", "sqlite3"] }, "sha512-jBe37K7d8ZSKptdKfakQFdeljtu3P2Cbo7tJoJSVZADzIKOBo9IAJPOmMsH2bZl90bZgh8FQlD8BjxXA/zuBkQ=="], + + "esbuild": ["esbuild@0.25.9", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.9", "@esbuild/android-arm": "0.25.9", "@esbuild/android-arm64": "0.25.9", "@esbuild/android-x64": "0.25.9", "@esbuild/darwin-arm64": "0.25.9", "@esbuild/darwin-x64": "0.25.9", "@esbuild/freebsd-arm64": "0.25.9", "@esbuild/freebsd-x64": "0.25.9", "@esbuild/linux-arm": "0.25.9", "@esbuild/linux-arm64": "0.25.9", "@esbuild/linux-ia32": "0.25.9", "@esbuild/linux-loong64": "0.25.9", "@esbuild/linux-mips64el": "0.25.9", "@esbuild/linux-ppc64": "0.25.9", "@esbuild/linux-riscv64": "0.25.9", "@esbuild/linux-s390x": "0.25.9", "@esbuild/linux-x64": "0.25.9", "@esbuild/netbsd-arm64": "0.25.9", "@esbuild/netbsd-x64": "0.25.9", "@esbuild/openbsd-arm64": "0.25.9", "@esbuild/openbsd-x64": "0.25.9", "@esbuild/openharmony-arm64": "0.25.9", "@esbuild/sunos-x64": "0.25.9", "@esbuild/win32-arm64": "0.25.9", "@esbuild/win32-ia32": "0.25.9", "@esbuild/win32-x64": "0.25.9" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-CRbODhYyQx3qp7ZEwzxOk4JBqmD/seJrzPa/cGjY1VtIn5E09Oi9/dB4JwctnfZ8Q8iT7rioVv5k/FNT/uf54g=="], + + "esbuild-register": ["esbuild-register@3.6.0", "", { "dependencies": { "debug": "^4.3.4" }, "peerDependencies": { "esbuild": ">=0.12 <1" } }, "sha512-H2/S7Pm8a9CL1uhp9OvjwrBh5Pvx0H8qVOxNu8Wed9Y7qv56MPtq+GGM8RJpq6glYJn9Wspr8uw7l55uyinNeg=="], + + "get-tsconfig": ["get-tsconfig@4.10.1", "", { "dependencies": { "resolve-pkg-maps": "^1.0.0" } }, "sha512-auHyJ4AgMz7vgS8Hp3N6HXSmlMdUyhSUrfBF16w153rxtLIEOE+HGqaBppczZvnHLqQJfiHotCYpNhl0lUROFQ=="], + "kleur": ["kleur@4.1.5", "", {}, "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ=="], "klona": ["klona@2.0.6", "", {}, "sha512-dhG34DXATL5hSxJbIexCft8FChFXtmskoZYnoPWjXQuebWYCNkVeV3KkGegCK9CP1oswI/vQibS2GY7Em/sJJA=="], + "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], + "node-fetch": ["node-fetch@2.7.0", "", { "dependencies": { "whatwg-url": "^5.0.0" }, "peerDependencies": { "encoding": "^0.1.0" }, "optionalPeers": ["encoding"] }, "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A=="], - "pocketbase": ["pocketbase@0.26.1", "", {}, "sha512-fjcPDpxyqTZCwqGUTPUV7vssIsNMqHxk9GxbhxYHPEf18RqX2d9cpSqbbHk7aas30jqkgptuKfG7aY/Mytjj3g=="], + "pg": ["pg@8.16.3", "", { "dependencies": { "pg-connection-string": "^2.9.1", "pg-pool": "^3.10.1", "pg-protocol": "^1.10.3", "pg-types": "2.2.0", "pgpass": "1.0.5" }, "optionalDependencies": { "pg-cloudflare": "^1.2.7" }, "peerDependencies": { "pg-native": ">=3.0.1" }, "optionalPeers": ["pg-native"] }, "sha512-enxc1h0jA/aq5oSDMvqyW3q89ra6XIIDZgCX9vkMrnz5DFTw/Ny3Li2lFQ+pt3L6MCgm/5o2o8HW9hiJji+xvw=="], + + "pg-cloudflare": ["pg-cloudflare@1.2.7", "", {}, "sha512-YgCtzMH0ptvZJslLM1ffsY4EuGaU0cx4XSdXLRFae8bPP4dS5xL1tNB3k2o/N64cHJpwU7dxKli/nZ2lUa5fLg=="], + + "pg-connection-string": ["pg-connection-string@2.9.1", "", {}, "sha512-nkc6NpDcvPVpZXxrreI/FOtX3XemeLl8E0qFr6F2Lrm/I8WOnaWNhIPK2Z7OHpw7gh5XJThi6j6ppgNoaT1w4w=="], + + "pg-int8": ["pg-int8@1.0.1", "", {}, "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw=="], + + "pg-pool": ["pg-pool@3.10.1", "", { "peerDependencies": { "pg": ">=8.0" } }, "sha512-Tu8jMlcX+9d8+QVzKIvM/uJtp07PKr82IUOYEphaWcoBhIYkoHpLXN3qO59nAI11ripznDsEzEv8nUxBVWajGg=="], + + "pg-protocol": ["pg-protocol@1.10.3", "", {}, "sha512-6DIBgBQaTKDJyxnXaLiLR8wBpQQcGWuAESkRBX/t6OwA8YsqP+iVSiond2EDy6Y/dsGk8rh/jtax3js5NeV7JQ=="], + + "pg-types": ["pg-types@2.2.0", "", { "dependencies": { "pg-int8": "1.0.1", "postgres-array": "~2.0.0", "postgres-bytea": "~1.0.0", "postgres-date": "~1.0.4", "postgres-interval": "^1.1.0" } }, "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA=="], + + "pgpass": ["pgpass@1.0.5", "", { "dependencies": { "split2": "^4.1.0" } }, "sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug=="], + + "postgres-array": ["postgres-array@2.0.0", "", {}, "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA=="], + + "postgres-bytea": ["postgres-bytea@1.0.0", "", {}, "sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w=="], + + "postgres-date": ["postgres-date@1.0.7", "", {}, "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q=="], + + "postgres-interval": ["postgres-interval@1.2.0", "", { "dependencies": { "xtend": "^4.0.0" } }, "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ=="], + + "resolve-pkg-maps": ["resolve-pkg-maps@1.0.0", "", {}, "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw=="], "retry": ["retry@0.13.1", "", {}, "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg=="], + "source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="], + + "source-map-support": ["source-map-support@0.5.21", "", { "dependencies": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" } }, "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w=="], + + "split2": ["split2@4.2.0", "", {}, "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg=="], + "tr46": ["tr46@0.0.3", "", {}, "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="], "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], @@ -84,5 +190,53 @@ "whatwg-url": ["whatwg-url@5.0.0", "", { "dependencies": { "tr46": "~0.0.3", "webidl-conversions": "^3.0.0" } }, "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw=="], "ws": ["ws@8.18.3", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg=="], + + "xtend": ["xtend@4.0.2", "", {}, "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ=="], + + "@esbuild-kit/core-utils/esbuild": ["esbuild@0.18.20", "", { "optionalDependencies": { "@esbuild/android-arm": "0.18.20", "@esbuild/android-arm64": "0.18.20", "@esbuild/android-x64": "0.18.20", "@esbuild/darwin-arm64": "0.18.20", "@esbuild/darwin-x64": "0.18.20", "@esbuild/freebsd-arm64": "0.18.20", "@esbuild/freebsd-x64": "0.18.20", "@esbuild/linux-arm": "0.18.20", "@esbuild/linux-arm64": "0.18.20", "@esbuild/linux-ia32": "0.18.20", "@esbuild/linux-loong64": "0.18.20", "@esbuild/linux-mips64el": "0.18.20", "@esbuild/linux-ppc64": "0.18.20", "@esbuild/linux-riscv64": "0.18.20", "@esbuild/linux-s390x": "0.18.20", "@esbuild/linux-x64": "0.18.20", "@esbuild/netbsd-x64": "0.18.20", "@esbuild/openbsd-x64": "0.18.20", "@esbuild/sunos-x64": "0.18.20", "@esbuild/win32-arm64": "0.18.20", "@esbuild/win32-ia32": "0.18.20", "@esbuild/win32-x64": "0.18.20" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.18.20", "", { "os": "android", "cpu": "arm" }, "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/android-arm64": ["@esbuild/android-arm64@0.18.20", "", { "os": "android", "cpu": "arm64" }, "sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/android-x64": ["@esbuild/android-x64@0.18.20", "", { "os": "android", "cpu": "x64" }, "sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.18.20", "", { "os": "darwin", "cpu": "arm64" }, "sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.18.20", "", { "os": "darwin", "cpu": "x64" }, "sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.18.20", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.18.20", "", { "os": "freebsd", "cpu": "x64" }, "sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/linux-arm": ["@esbuild/linux-arm@0.18.20", "", { "os": "linux", "cpu": "arm" }, "sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.18.20", "", { "os": "linux", "cpu": "arm64" }, "sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.18.20", "", { "os": "linux", "cpu": "ia32" }, "sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.18.20", "", { "os": "linux", "cpu": "none" }, "sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.18.20", "", { "os": "linux", "cpu": "none" }, "sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.18.20", "", { "os": "linux", "cpu": "ppc64" }, "sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.18.20", "", { "os": "linux", "cpu": "none" }, "sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.18.20", "", { "os": "linux", "cpu": "s390x" }, "sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/linux-x64": ["@esbuild/linux-x64@0.18.20", "", { "os": "linux", "cpu": "x64" }, "sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.18.20", "", { "os": "none", "cpu": "x64" }, "sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.18.20", "", { "os": "openbsd", "cpu": "x64" }, "sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.18.20", "", { "os": "sunos", "cpu": "x64" }, "sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.18.20", "", { "os": "win32", "cpu": "arm64" }, "sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.18.20", "", { "os": "win32", "cpu": "ia32" }, "sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.18.20", "", { "os": "win32", "cpu": "x64" }, "sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ=="], } } diff --git a/compose.yml b/compose.yml index be1c89c..65df7ca 100644 --- a/compose.yml +++ b/compose.yml @@ -9,12 +9,26 @@ services: - ./db/redis:/data environment: - VALKEY_EXTRA_FLAGS=--save 60 1 - pocketbase: - container_name: qweribot-pocketbase - build: - context: ./pocketbase + # pocketbase: + # container_name: qweribot-pocketbase + # build: + # context: ./pocketbase + # ports: + # - 8090:8090 + # restart: no + # volumes: + # - ./db/pocketbase:/pb/pb_data + postgres: + container_name: postgres + image: postgres:latest + restart: unless-stopped + env_file: + - .env.development + # environment: + # POSTGRES_USER: $$POSTGRES_USER + # POSTGRES_PASSWORD: $$POSTGRES_PASSWORD + # POSTGRES_DB: $$POSTGRES_DB ports: - - 8090:8090 - restart: no + - "5432:5432" volumes: - - ./db/pocketbase:/pb/pb_data + - ./db/postgresql:/var/lib/postgresql/data diff --git a/drizzle.config.ts b/drizzle.config.ts new file mode 100644 index 0000000..cfe6eef --- /dev/null +++ b/drizzle.config.ts @@ -0,0 +1,9 @@ +import { defineConfig } from "drizzle-kit"; + +const url = `postgresql://admin:abcdefgh@localhost:5432/twitchbot`; + +export default defineConfig({ + dialect: 'postgresql', + schema: './src/db/schema.ts', + dbCredentials: { url } +}); diff --git a/package.json b/package.json index 2feb889..72c6fd4 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,9 @@ "name": "qweribot", "module": "src/index.ts", "devDependencies": { - "@types/bun": "latest" + "@types/bun": "latest", + "drizzle-kit": "^0.31.4", + "pg": "^8.16.3" }, "peerDependencies": { "typescript": "^5.8.3" @@ -13,7 +15,7 @@ "@fontsource/jersey-15": "^5.2.6", "@twurple/auth": "^7.3.0", "@twurple/eventsub-ws": "^7.3.0", - "kleur": "^4.1.5", - "pocketbase": "^0.26.1" + "drizzle-orm": "^0.44.5", + "kleur": "^4.1.5" } } diff --git a/src/commands/getloot.ts b/src/commands/getloot.ts index 1561a56..2e53e29 100644 --- a/src/commands/getloot.ts +++ b/src/commands/getloot.ts @@ -1,7 +1,7 @@ import { redis } from "bun"; import { Command, sendMessage } from "commands"; import { getUserRecord, updateUserRecord } from "db/dbUser"; -import items from "items"; +import items, { type inventory, type items } from "items"; import { buildTimeString } from "lib/dateManager"; import { timeout } from "lib/timeout"; import { isInvuln, removeInvuln } from "lib/invuln"; @@ -19,7 +19,7 @@ export default new Command({ if (await isInvuln(msg.chatterId) && !streamerUsers.includes(msg.chatterId)) { await sendMessage(`You're no longer an invuln because used a lootbox.`, msg.messageId); await removeInvuln(msg.chatterId); }; if (await user.itemLock()) { await sendMessage(`Cannot get loot (itemlock)`, msg.messageId); return; }; const userData = await getUserRecord(user); - const lastlootbox = Date.parse(userData.lastlootbox); + const lastlootbox = userData.lastlootbox.getTime(); const now = Date.now(); if ((lastlootbox + COOLDOWN) > now) { if (await user.greedy()) { @@ -40,12 +40,12 @@ export default new Command({ await user.clearGreed(); await user.setLock(); - userData.lastlootbox = new Date(now).toISOString(); + userData.lastlootbox = new Date(now); const gainedqbucks = Math.floor(Math.random() * 100) + 50; // range from 50 to 150 userData.balance += gainedqbucks; - const itemDiff = { + const itemDiff: inventory = { grenade: 0, blaster: 0, tnt: 0, @@ -53,13 +53,13 @@ export default new Command({ }; for (let i = 0; i < 5; i++) { - if (Math.floor(Math.random() * 5) === 0) itemDiff.grenade += 1; - if (Math.floor(Math.random() * 5) === 0) itemDiff.blaster += 1; - if (Math.floor(Math.random() * 25) === 0) itemDiff.tnt += 1; - if (Math.floor(Math.random() * 250) === 0) itemDiff.silverbullet += 1; + if (Math.floor(Math.random() * 5) === 0) itemDiff.grenade! += 1; + if (Math.floor(Math.random() * 5) === 0) itemDiff.blaster! += 1; + if (Math.floor(Math.random() * 25) === 0) itemDiff.tnt! += 1; + if (Math.floor(Math.random() * 250) === 0) itemDiff.silverbullet! += 1; }; - for (const [item, amount] of Object.entries(itemDiff)) { + for (const [item, amount] of Object.entries(itemDiff) as [items, number]) { if (userData.inventory[item]) userData.inventory[item] += amount; else userData.inventory[item] = amount; }; diff --git a/src/connectionCheck.ts b/src/connectionCheck.ts index 3308a24..74807a3 100644 --- a/src/connectionCheck.ts +++ b/src/connectionCheck.ts @@ -1,11 +1,9 @@ -import pocketbase from "db/connection"; import { RedisClient } from "bun"; import logger from "lib/logger"; export async function connectionCheck() { let pbstatus = false; try { - await pocketbase.health.check().then(a => a.code === 200); pbstatus = true; } catch { }; const tempclient = new RedisClient(undefined, { diff --git a/src/db/connection.ts b/src/db/connection.ts index ef6fd3f..305f21b 100644 --- a/src/db/connection.ts +++ b/src/db/connection.ts @@ -1,75 +1,18 @@ -import type { AccessToken } from "@twurple/auth"; -import PocketBase, { RecordService } from "pocketbase"; -import type { inventory } from "items"; -import logger from "lib/logger"; +import * as schema from "db/schema"; -const pocketbaseurl = process.env.POCKETBASE_URL ?? "localhost:8090"; -if (pocketbaseurl === "") { logger.enverr("POCKETBASE_URL"); process.exit(1); }; +// const host = process.env.POSTGRES_HOST ?? ""; +// if (!host) { logger.enverr("POSTGRES_HOST"); process.exit(1); }; +// +// const user = process.env.POSTGRES_USER ?? ""; +// if (!user) { logger.enverr("POSTGRES_USER"); process.exit(1); }; +// const password = process.env.POSTGRES_PASSWORD ?? ""; +// if (!password) { logger.enverr("POSTGRES_USER"); process.exit(1); }; +// const database = process.env.POSTGRES_DB ?? "twitchbot"; +// +// const connection = { host, user, password, database }; +const url = `postgresql://admin:abcdefgh@localhost:5432/twitchbot`; -export type authRecord = { - id: string; - accesstoken: AccessToken; -}; - -export type userRecord = { - id: string; - username: string; // Don't use this, Use User.username or User.displayName. This is just to make the pocketbase data easier to read. - balance: number; - inventory: inventory; - lastlootbox: string; -}; - -export type usedItemRecord = { - id?: string; - user: string; - item: string; - created: string; -}; - -export type timeoutRecord = { - id?: string; - user: string; - target: string; - item: string; - created: string; -}; - -export type cheerEventRecord = { - id?: string; - user: string; - cheer: string; - created: string; -}; - -export type cheerRecord = { - id?: string; - user: string; - amount: number; -}; - -export type anivTimeoutRecord = { - id?: string; - message: string; - user: string; - duration: number; -}; - -export type getLootRecord = { - id?: string; - user: string; - qbucks: number; - items: inventory; -}; - -interface TypedPocketBase extends PocketBase { - collection(idOrName: 'auth'): RecordService; - collection(idOrName: 'users'): RecordService; - collection(idOrName: 'usedItems'): RecordService; - collection(idOrName: 'timeouts'): RecordService; - collection(idOrName: 'cheerEvents'): RecordService; - collection(idOrName: 'cheers'): RecordService; - collection(idOrName: 'anivTimeouts'): RecordService; - collection(idOrName: 'getLoots'): RecordService; -}; - -export default new PocketBase(pocketbaseurl).autoCancellation(false) as TypedPocketBase; +import { drizzle } from 'drizzle-orm/bun-sql'; +export default drizzle(url, { + schema +}); diff --git a/src/db/dbAnivTimeouts.ts b/src/db/dbAnivTimeouts.ts index c070630..069354d 100644 --- a/src/db/dbAnivTimeouts.ts +++ b/src/db/dbAnivTimeouts.ts @@ -1,14 +1,13 @@ -import pocketbase from "db/connection"; +import db from "db/connection"; import User from "user"; -import logger from "lib/logger"; +import { anivTimeouts } from "db/schema"; +import { type anivBots } from "lib/handleAnivMessage"; -const pb = pocketbase.collection('anivTimeouts'); - -export async function createAnivTimeoutRecord(message: string, user: User, duration: number) { - try { - await pb.create({ message, user: user.id, duration }); - } catch (e) { - logger.err(`Failed to create anivTimeoutRecord: user: ${user.displayName} message: "${message}" duration: ${duration}`); - logger.err(e as string); - }; +export async function createAnivTimeoutRecord(message: string, anivbot: anivBots, user: User, duration: number) { + await db.insert(anivTimeouts).values({ + message, + anivBot: anivbot, + user: parseInt(user.id), + duration + }); }; diff --git a/src/db/dbAuth.ts b/src/db/dbAuth.ts index 140fa0b..bf7d1ca 100644 --- a/src/db/dbAuth.ts +++ b/src/db/dbAuth.ts @@ -1,38 +1,28 @@ import type { AccessToken } from "@twurple/auth"; -import pocketbase, { type authRecord } from "db/connection"; -const pb = pocketbase.collection('auth'); +import db from "db/connection"; +import { auth } from "db/schema"; +import { eq } from "drizzle-orm"; export async function createAuthRecord(token: AccessToken, userId: string) { - try { - const data: authRecord = { - accesstoken: token, - id: userId - }; - await pb.create(data); - } catch (err) { }; + await db.insert(auth).values({ + id: parseInt(userId), + accesstoken: token + }); }; export async function getAuthRecord(userId: string, requiredIntents: string[]) { - try { - const data = await pb.getOne(userId); - if (!requiredIntents.every(intent => data.accesstoken.scope.includes(intent))) return undefined; - return { accesstoken: data.accesstoken }; - } catch (err) { - return undefined; - }; + const data = await db.query.auth.findFirst({ + where: eq(auth.id, parseInt(userId)) + }); + if (!data) return undefined; + if (!requiredIntents.every(intent => data.accesstoken.scope.includes(intent))) return undefined; + return { accesstoken: data.accesstoken }; }; export async function updateAuthRecord(userId: string, newtoken: AccessToken) { - try { - const newrecord = { - accesstoken: newtoken, - }; - await pb.update(userId, newrecord); - } catch (err) { }; + await db.update(auth).set({ accesstoken: newtoken }).where(eq(auth.id, parseInt(userId))); }; export async function deleteAuthRecord(userId: string): Promise { - try { - await pb.delete(userId); - } catch (err) { }; + await db.delete(auth).where(eq(auth.id, parseInt(userId))); }; diff --git a/src/db/dbCheerEvents.ts b/src/db/dbCheerEvents.ts index 3da6ff5..bab77a2 100644 --- a/src/db/dbCheerEvents.ts +++ b/src/db/dbCheerEvents.ts @@ -1,26 +1,20 @@ -import pocketbase from "db/connection"; +import db from "db/connection"; +import { cheerEvents } from "db/schema"; +import { and, between, eq, SQL } from "drizzle-orm"; +import type { items } from "items"; import User from "user"; -import logger from "lib/logger"; -const pb = pocketbase.collection('cheerEvents'); -export async function createCheerEventRecord(user: User, cheer: string): Promise { - try { - await pb.create({ user: user.id, cheer }); - } catch (e) { - logger.err(`Failed to create cheerEvent record in database: user: ${user.id}, cheer: ${cheer}`); - logger.err(e as string); - }; +export async function createCheerEventRecord(user: User, cheer: items): Promise { + await db.insert(cheerEvents).values({ user: parseInt(user.id), event: cheer }); }; export async function getCheerEvents(user: User, monthData?: string) { - try { - const monthquery = monthData ? ` && created~"${monthData}"` : ''; - const data = await pb.getFullList({ - filter: `user="${user.id}"${monthquery}` - }); - return data; - } catch (e) { - logger.err(`Failed to get cheerEvents for user: ${user.id}, month: ${monthData}`); - logger.err(e as string); + let condition: SQL | undefined = eq(cheerEvents.user, parseInt(user.id)); + if (monthData) { + const begin = Date.parse(monthData); + const end = new Date(begin).setMonth(new Date(begin).getMonth() + 1); + condition = and(condition, between(cheerEvents.created, new Date(begin), new Date(end))); }; + const data = await db.select().from(cheerEvents).where(condition); + return data; }; diff --git a/src/db/dbCheers.ts b/src/db/dbCheers.ts index 44cb29f..c104610 100644 --- a/src/db/dbCheers.ts +++ b/src/db/dbCheers.ts @@ -1,26 +1,19 @@ -import pocketbase from "db/connection"; +import db from "db/connection"; +import { cheers } from "db/schema"; import User from "user"; -import logger from "lib/logger"; -const pb = pocketbase.collection('cheers'); +import { and, between, eq, SQL } from "drizzle-orm"; export async function createCheerRecord(user: User, amount: number): Promise { - try { - await pb.create({ user: user.id, amount }) - } catch (e) { - logger.err(`Failed to create cheer record in database: user: ${user.id}, amount: ${amount}`); - logger.err(e as string); - }; + await db.insert(cheers).values({ user: parseInt(user.id), amount }); }; export async function getCheers(user: User, monthData?: string) { - try { - const monthquery = monthData ? ` && created~"${monthData}"` : ''; - const data = await pb.getFullList({ - filter: `user="${user.id}"${monthquery}` - }); - return data; - } catch (e) { - logger.err(`Failed to get cheers for user: ${user.id}, month: ${monthData}`); - logger.err(e as string); + let condition: SQL | undefined = eq(cheers.user, parseInt(user.id)); + if (monthData) { + const begin = Date.parse(monthData); + const end = new Date(begin).setMonth(new Date(begin).getMonth() + 1); + condition = and(condition, between(cheers.created, new Date(begin), new Date(end))); }; + const data = await db.select().from(cheers).where(condition); + return data; }; diff --git a/src/db/dbGetLoot.ts b/src/db/dbGetLoot.ts index 397032f..498908e 100644 --- a/src/db/dbGetLoot.ts +++ b/src/db/dbGetLoot.ts @@ -1,19 +1,13 @@ -import pocketbase from "db/connection"; +import db from "db/connection"; +import { getLoots } from "db/schema"; import type { inventory } from "items"; -import logger from "lib/logger"; import type User from "user"; -const pb = pocketbase.collection('getLoots'); export async function createGetLootRecord(user: User, qbucks: number, inventory: inventory) { - try { - await pb.create({ - user: user.id, - qbucks, - items: inventory - }); - } catch (e) { - logger.err(`Failed to create getLoot record for ${user.displayName}: ${inventory}`); - logger.err(e as string); - }; + await db.insert(getLoots).values({ + user: parseInt(user.id), + qbucks: qbucks, + items: inventory + }); }; diff --git a/src/db/dbTimeouts.ts b/src/db/dbTimeouts.ts index 2d3e9a9..71d8aa4 100644 --- a/src/db/dbTimeouts.ts +++ b/src/db/dbTimeouts.ts @@ -1,39 +1,35 @@ -import pocketbase from "db/connection"; +import db from "db/connection"; +import { timeouts } from "db/schema"; import User from "user"; -import logger from "lib/logger"; -const pb = pocketbase.collection('timeouts'); +import type { items } from "items"; +import { and, between, eq, type SQL } from "drizzle-orm"; -export async function createTimeoutRecord(user: User, target: User, item: string): Promise { - try { - await pb.create({ user: user.id, target: target.id, item }); - } catch (err) { - logger.err(`Failed to create timeout record in database: user: ${user.id}, target: ${target.id}, item: ${item}`); - logger.err(err as string); - }; +export async function createTimeoutRecord(user: User, target: User, item: items): Promise { + await db.insert(timeouts).values({ + user: parseInt(user.id), + target: parseInt(target.id), + item + }); }; export async function getTimeoutsAsUser(user: User, monthData?: string) { - try { - const monthquery = monthData ? ` && created~"${monthData}"` : ''; - const data = await pb.getFullList({ - filter: `user="${user.id}"${monthquery}` - }); - return data; - } catch (e) { - logger.err(`Failed to get timeouts as user: ${user.id}, month: ${monthData}`); - logger.err(e as string); + let condition: SQL | undefined = eq(timeouts.user, parseInt(user.id)); + if (monthData) { + const begin = Date.parse(monthData); + const end = new Date(begin).setMonth(new Date(begin).getMonth() + 1); + condition = and(condition, between(timeouts.created, new Date(begin), new Date(end))); }; + const data = await db.select().from(timeouts).where(condition); + return data; }; export async function getTimeoutsAsTarget(user: User, monthData?: string) { - try { - const monthquery = monthData ? ` && created~"${monthData}"` : ''; - const data = await pb.getFullList({ - filter: `target="${user.id}"${monthquery}` - }); - return data; - } catch (e) { - logger.err(`Failed to get timeouts as target: ${user.id}, month: ${monthData}`); - logger.err(e as string); + let condition: SQL | undefined = eq(timeouts.target, parseInt(user.id)); + if (monthData) { + const begin = Date.parse(monthData); + const end = new Date(begin).setMonth(new Date(begin).getMonth() + 1); + condition = and(condition, between(timeouts.created, new Date(begin), new Date(end))); }; + const data = await db.select().from(timeouts).where(condition); + return data; }; diff --git a/src/db/dbUsedItems.ts b/src/db/dbUsedItems.ts index 5751236..33e1f87 100644 --- a/src/db/dbUsedItems.ts +++ b/src/db/dbUsedItems.ts @@ -1,27 +1,20 @@ -import pocketbase from "db/connection"; +import db from "db/connection"; +import { usedItems } from "db/schema"; import User from "user"; -import logger from "lib/logger"; -const pb = pocketbase.collection('usedItems'); +import type { items } from "items"; +import { and, between, eq, type SQL } from "drizzle-orm"; -export async function createUsedItemRecord(user: User, item: string): Promise { - try { - await pb.create({ user: user.id, item }); - } catch (err) { - logger.err(`Failed to create usedItem record in database: user: ${user.id}, item: ${item}`); - logger.err(err as string); - }; +export async function createUsedItemRecord(user: User, item: items): Promise { + await db.insert(usedItems).values({ user: parseInt(user.id), item }); }; export async function getItemsUsed(user: User, monthData?: string) { - try { - const monthquery = monthData ? ` && created~"${monthData}"` : ''; - const data = await pb.getFullList({ - filter: `user="${user.id}"${monthquery}` - }); - return data; - } catch (e) { - logger.err(`Failed to get items used for user: ${user.id}, month: ${monthData}`); - logger.err(e as string); + let condition: SQL | undefined = eq(usedItems.user, parseInt(user.id)); + if (monthData) { + const begin = Date.parse(monthData); + const end = new Date(begin).setMonth(new Date(begin).getMonth() + 1); + condition = and(condition, between(usedItems.created, new Date(begin), new Date(end))); }; + const data = await db.select().from(usedItems).where(condition); + return data; }; - diff --git a/src/db/dbUser.ts b/src/db/dbUser.ts index e472a9f..fc08734 100644 --- a/src/db/dbUser.ts +++ b/src/db/dbUser.ts @@ -1,58 +1,47 @@ -import pocketbase, { type userRecord } from "db/connection"; -import { emptyInventory, itemarray } from "items"; +import db from "db/connection"; +import { users } from "db/schema"; +import { itemarray, type inventory } from "items"; import type User from "user"; import logger from "lib/logger"; - -const pb = pocketbase.collection('users'); +import { desc, eq } from "drizzle-orm"; /** Use this function to both ensure existance and to retreive data */ -export async function getUserRecord(user: User): Promise { - try { - const data = await pb.getOne(user.id); +export async function getUserRecord(user: User) { + const data = await db.query.users.findFirst({ where: eq(users.id, parseInt(user.id)) }); + if (!data) return createUserRecord(user); - if (Object.keys(data.inventory).sort().toString() !== itemarray.sort().toString()) { // If the items in the user inventory are missing an item. - itemarray.forEach(key => { - if (!(key in data.inventory)) data.inventory[key] = 0; - }); - }; - - return data; - } catch (err) { - // This gets triggered if the user doesn't exist in the database - return await createUserRecord(user); + if (Object.keys(data.inventory).sort().toString() !== itemarray.sort().toString()) { // If the items in the user inventory are missing an item. + itemarray.forEach(key => { + if (!(key in data.inventory)) data.inventory[key] = 0; + }); }; -}; - -export async function getAllUserRecords(): Promise { - return await pb.getFullList(); -}; - -async function createUserRecord(user: User): Promise { - const data = await pb.create({ - id: user.id, - username: user.username, - balance: 0, - inventory: emptyInventory, - lastlootbox: new Date(0).toISOString() - }); return data; }; -export async function updateUserRecord(user: User, newData: userRecord): Promise { - try { - await pb.update(user.id, newData); - return true; - } catch (err) { - logger.err(err as string); - return false; - }; +export async function getAllUserRecords() { + return await db.select().from(users); +}; + +async function createUserRecord(user: User) { + return await db.insert(users).values({ + id: parseInt(user.id), + username: user.username + }).returning().then(a => { + if (!a[0]) throw Error('Something went horribly wrong'); + return a[0] + }); +}; + +export type balanceUpdate = { balance: number; }; +export type inventoryUpdate = { inventory: inventory; }; +type updateUser = balanceUpdate | inventoryUpdate; + +export async function updateUserRecord(user: User, newData: updateUser) { + await db.update(users).set(newData).where(eq(users.id, parseInt(user.id))); + return true; }; export async function getBalanceLeaderboard() { - try { - return await pb.getList(1, 10, { sort: '-balance,id' }).then(a => a.items); - } catch (err) { - logger.err(err as string); - }; + return await db.select().from(users).orderBy(desc(users.balance)).limit(10); }; diff --git a/src/db/schema.ts b/src/db/schema.ts new file mode 100644 index 0000000..b1b4c22 --- /dev/null +++ b/src/db/schema.ts @@ -0,0 +1,64 @@ +import type { AccessToken } from "@twurple/auth"; +import type { inventory, items } from "items"; + +import { integer, jsonb, pgTable, timestamp, uuid, varchar } from "drizzle-orm/pg-core"; +import type { anivBots } from "lib/handleAnivMessage"; + +export const auth = pgTable('auth', { + id: integer().primaryKey(), + accesstoken: jsonb().$type().notNull() +}); + +export const users = pgTable('users', { + id: integer().primaryKey().notNull(), + username: varchar().notNull(), + balance: integer().default(0).notNull(), + inventory: jsonb().$type().default({}).notNull(), + lastlootbox: timestamp().default(new Date(0)).notNull() +}); + +export const timeouts = pgTable('timeouts', { + id: uuid().defaultRandom().primaryKey(), + user: integer().notNull().references(() => users.id), + target: integer().notNull().references(() => users.id), + item: varchar().$type().notNull(), + created: timestamp().defaultNow().notNull() +}); + +export const usedItems = pgTable('usedItems', { + id: uuid().defaultRandom().primaryKey(), + user: integer().notNull().references(() => users.id), + item: varchar().$type().notNull(), + created: timestamp().defaultNow().notNull() +}); + +export const cheerEvents = pgTable('cheerEvents', { + id: uuid().defaultRandom().primaryKey(), + user: integer().notNull().references(() => users.id), + event: varchar().$type().notNull(), + created: timestamp().defaultNow().notNull() +}); + +export const cheers = pgTable('cheers', { + id: uuid().defaultRandom().primaryKey(), + user: integer().notNull().references(() => users.id), + amount: integer().notNull(), + created: timestamp().defaultNow().notNull() +}); + +export const anivTimeouts = pgTable('anivTimeouts', { + id: uuid().defaultRandom().primaryKey(), + user: integer().notNull().references(() => users.id), + message: varchar().notNull(), + anivBot: varchar().$type().notNull(), + duration: integer().notNull(), + created: timestamp().defaultNow().notNull() +}); + +export const getLoots = pgTable('getLoots', { + id: uuid().defaultRandom().primaryKey(), + user: integer().notNull().references(() => users.id), + qbucks: integer().notNull(), + items: jsonb().$type().notNull(), + created: timestamp().defaultNow().notNull() +}); diff --git a/src/items/index.ts b/src/items/index.ts index 6805ac2..9192ca2 100644 --- a/src/items/index.ts +++ b/src/items/index.ts @@ -3,7 +3,7 @@ import User from "user"; import { type userType, type specialExecuteArgs } from "commands"; type itemOptions = { - name: string; + name: items; aliases: string[]; prettyName: string; plural: string; @@ -13,7 +13,7 @@ type itemOptions = { }; export class Item { - public readonly name: string; + public readonly name: items; public readonly prettyName: string; public readonly plural: string; public readonly description: string; @@ -25,7 +25,7 @@ export class Item { /** Creates an item object */ constructor(options: itemOptions) { - this.name = options.name.toLowerCase(); + this.name = options.name; this.prettyName = options.prettyName; this.plural = options.plural; this.description = options.description; @@ -38,12 +38,11 @@ export class Item { }; import { readdir } from 'node:fs/promises'; -import type { userRecord } from "db/connection"; -import { updateUserRecord } from "db/dbUser"; -const items = new Map; +import { updateUserRecord, type inventoryUpdate } from "db/dbUser"; +const itemMap = new Map; const specialAliasItems = new Map; const emptyInventory: inventory = {}; -const itemarray: string[] = []; +const itemarray: items[] = []; const files = await readdir(import.meta.dir); for (const file of files) { @@ -53,20 +52,22 @@ for (const file of files) { emptyInventory[item.name] = 0; itemarray.push(item.name); for (const alias of item.aliases) { - items.set(alias, item); // Since it's not a primitive type the map is filled with references to the item, not the actual object + itemMap.set(alias, item); // Since it's not a primitive type the map is filled with references to the item, not the actual object }; for (const alias of item.specialaliases) { specialAliasItems.set(alias, item); }; }; -export default items; +export default itemMap; export { emptyInventory, itemarray, specialAliasItems }; + +export type items = "blaster" | "silverbullet" | "grenade" | "tnt"; export type inventory = { - [key: string]: number; + [key in items]?: number; }; -export async function changeItemCount(user: User, userRecord: userRecord, itemname: string, amount = -1): Promise { +export async function changeItemCount(user: User, userRecord: inventoryUpdate, itemname: items, amount = -1): Promise { userRecord.inventory[itemname] = userRecord.inventory[itemname]! += amount; if (userRecord.inventory[itemname] < 0) return false; await updateUserRecord(user, userRecord); diff --git a/src/items/silverbullet.ts b/src/items/silverbullet.ts index d945795..8f546b9 100644 --- a/src/items/silverbullet.ts +++ b/src/items/silverbullet.ts @@ -20,7 +20,7 @@ export default new Item({ execution: async (msg, user, specialargs) => { const userObj = await getUserRecord(user); if (userObj.inventory[ITEMNAME]! < 1) { await sendMessage(`You don't have any silver bullets!`, msg.messageId); return; }; - const messagequery = parseCommandArgs(msg.messageText); + const messagequery = parseCommandArgs(msg.messageText, specialargs?.activation); if (!messagequery[0]) { await sendMessage('Please specify a target'); return; }; const target = await User.initUsername(messagequery[0].toLowerCase()); if (!target) { await sendMessage(`${messagequery[0]} doesn't exist`); return; }; diff --git a/src/lib/handleAnivMessage.ts b/src/lib/handleAnivMessage.ts index dccddc8..78aedb3 100644 --- a/src/lib/handleAnivMessage.ts +++ b/src/lib/handleAnivMessage.ts @@ -5,7 +5,7 @@ import { timeout } from "lib/timeout"; import { sendMessage } from "commands"; import { createAnivTimeoutRecord } from "db/dbAnivTimeouts"; -const ANIVNAMES = ['a_n_e_e_v', 'a_n_i_v']; +const ANIVNAMES: anivBots[] = ['a_n_e_e_v', 'a_n_i_v']; type anivMessageStore = { [key: string]: string; @@ -14,13 +14,15 @@ type anivMessageStore = { type IsAnivMessage = { isAnivMessage: true; message: string; - anivbot: string; + anivbot: anivBots; }; type isNotAnivMessage = { isAnivMessage: false; }; +export type anivBots = 'a_n_i_v' | 'a_n_e_e_v'; + type anivMessageResult = IsAnivMessage | isNotAnivMessage; async function isAnivMessage(message: string): Promise { @@ -34,7 +36,7 @@ async function isAnivMessage(message: string): Promise { }; export default async function handleMessage(msg: EventSubChannelChatMessageEvent, user: User) { - if (ANIVNAMES.includes(user.displayName)) { + if (ANIVNAMES.map(a => a.toLowerCase()).includes(user.username)) { const data: anivMessageStore = await redis.get('anivmessages').then(a => a === null ? {} : JSON.parse(a)); data[user.displayName] = msg.messageText; await redis.set('anivmessages', JSON.stringify(data)); @@ -43,7 +45,7 @@ export default async function handleMessage(msg: EventSubChannelChatMessageEvent if (data.isAnivMessage) await Promise.all([ timeout(user, 'copied an aniv message', 30), sendMessage(`${user.displayName} got timed out for copying an ${data.anivbot} message`), - createAnivTimeoutRecord(msg.messageText, user, 30) + createAnivTimeoutRecord(msg.messageText, data.anivbot, user, 30) ]); }; };