1
Fork 0

Implement migrations, and switch from sqlx to rusqlite

This commit is contained in:
prescientmoon 2024-08-22 22:11:21 +02:00
parent 7cdc3a2755
commit fee7fe77f8
Signed by: prescientmoon
SSH key fingerprint: SHA256:UUF9JT2s8Xfyv76b8ZuVL7XrmimH4o49p4b+iexbVH4
17 changed files with 629 additions and 1044 deletions

1
.gitignore vendored
View file

@ -10,3 +10,4 @@ shimmering/assets/b30_background.*
target
backups
dump.sql
schema.sql

686
Cargo.lock generated
View file

@ -60,12 +60,6 @@ version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4aa90d7ce82d4be67b64039a3d588d38dbcc6736577de4a847025ce5b0c468d1"
[[package]]
name = "allocator-api2"
version = "0.2.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f"
[[package]]
name = "android-tzdata"
version = "0.1.1"
@ -133,15 +127,6 @@ dependencies = [
"syn 2.0.66",
]
[[package]]
name = "atoi"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f28d99ec8bfea296261ca1af174f24225171fea9664ba9003cbebee704810528"
dependencies = [
"num-traits",
]
[[package]]
name = "autocfg"
version = "1.3.0"
@ -198,12 +183,6 @@ version = "0.22.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
[[package]]
name = "base64ct"
version = "1.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b"
[[package]]
name = "bit_field"
version = "0.10.2"
@ -218,12 +197,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "bitflags"
version = "2.5.0"
version = "2.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1"
dependencies = [
"serde",
]
checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de"
[[package]]
name = "bitstream-io"
@ -315,13 +291,13 @@ dependencies = [
[[package]]
name = "cc"
version = "1.0.99"
version = "1.1.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "96c51067fd44124faa7f870b4b1c969379ad32b2ba805aa959430ceaa384f695"
checksum = "72db2f7947ecee9b03b510377e8bb9077afa27176fdbff55c51027e976fdcc48"
dependencies = [
"jobserver",
"libc",
"once_cell",
"shlex",
]
[[package]]
@ -361,21 +337,6 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b"
[[package]]
name = "concurrent-queue"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973"
dependencies = [
"crossbeam-utils",
]
[[package]]
name = "const-oid"
version = "0.9.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8"
[[package]]
name = "core-foundation"
version = "0.9.4"
@ -437,21 +398,6 @@ dependencies = [
"libc",
]
[[package]]
name = "crc"
version = "3.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "69e6e4d7b33a94f0991c26729976b10ebde1d34c3ee82408fb536164fa10d636"
dependencies = [
"crc-catalog",
]
[[package]]
name = "crc-catalog"
version = "2.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5"
[[package]]
name = "crc32fast"
version = "1.4.2"
@ -489,15 +435,6 @@ dependencies = [
"crossbeam-utils",
]
[[package]]
name = "crossbeam-queue"
version = "0.3.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df0346b5d5e76ac2fe4e327c5fd1118d6be7c51dfb18f9b7922923f287471e35"
dependencies = [
"crossbeam-utils",
]
[[package]]
name = "crossbeam-utils"
version = "0.8.20"
@ -585,17 +522,6 @@ version = "2.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e8566979429cf69b49a5c740c60791108e86440e8be149bbea4fe54d2c32d6e2"
[[package]]
name = "der"
version = "0.7.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f55bf8e7b65898637379c1b74eb1551107c8294ed26d855ceb9fd1a09cfc9bc0"
dependencies = [
"const-oid",
"pem-rfc7468",
"zeroize",
]
[[package]]
name = "deranged"
version = "0.3.11"
@ -624,9 +550,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
dependencies = [
"block-buffer",
"const-oid",
"crypto-common",
"subtle",
]
[[package]]
@ -659,12 +583,6 @@ dependencies = [
"libloading",
]
[[package]]
name = "dotenvy"
version = "0.15.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b"
[[package]]
name = "dwrote"
version = "0.11.0"
@ -682,9 +600,6 @@ name = "either"
version = "1.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3dca9240753cf90908d7e4aac30f630662b02aebaa1b58a3cadabdb23385b58b"
dependencies = [
"serde",
]
[[package]]
name = "encoding_rs"
@ -720,28 +635,6 @@ dependencies = [
"version_check",
]
[[package]]
name = "etcetera"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "136d1b5283a1ab77bd9257427ffd09d8667ced0570b6f938942bc7568ed5b943"
dependencies = [
"cfg-if",
"home",
"windows-sys 0.48.0",
]
[[package]]
name = "event-listener"
version = "5.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6032be9bd27023a771701cc49f9f053c751055f71efb2e0ae5c15809093675ba"
dependencies = [
"concurrent-queue",
"parking",
"pin-project-lite",
]
[[package]]
name = "exr"
version = "1.72.0"
@ -758,6 +651,18 @@ dependencies = [
"zune-inflate",
]
[[package]]
name = "fallible-iterator"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649"
[[package]]
name = "fallible-streaming-iterator"
version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a"
[[package]]
name = "fastrand"
version = "2.1.0"
@ -795,9 +700,7 @@ version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "55ac459de2512911e4b674ce33cf20befaba382d05b62b008afc1c8b57cbf181"
dependencies = [
"futures-core",
"futures-sink",
"spin 0.9.8",
"spin",
]
[[package]]
@ -812,7 +715,7 @@ version = "0.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2845a73bbd781e691ab7c2a028c579727cd254942e8ced57ff73e0eafd60de87"
dependencies = [
"bitflags 2.5.0",
"bitflags 2.6.0",
"byteorder",
"core-foundation",
"core-graphics",
@ -873,7 +776,7 @@ version = "0.36.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5442dee36ca09604133580dc0553780e867936bb3cbef3275859e889026d2b17"
dependencies = [
"bitflags 2.5.0",
"bitflags 2.6.0",
"freetype-sys",
"libc",
]
@ -919,28 +822,6 @@ version = "0.3.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d"
[[package]]
name = "futures-executor"
version = "0.3.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d"
dependencies = [
"futures-core",
"futures-task",
"futures-util",
]
[[package]]
name = "futures-intrusive"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d930c203dd0b6ff06e0201a4a2fe9149b43c684fd4420555b26d21b1a02956f"
dependencies = [
"futures-core",
"lock_api",
"parking_lot",
]
[[package]]
name = "futures-io"
version = "0.3.30"
@ -1088,7 +969,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
dependencies = [
"ahash",
"allocator-api2",
]
[[package]]
@ -1112,39 +992,6 @@ version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024"
[[package]]
name = "hex"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
[[package]]
name = "hkdf"
version = "0.12.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7"
dependencies = [
"hmac",
]
[[package]]
name = "hmac"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e"
dependencies = [
"digest",
]
[[package]]
name = "home"
version = "0.5.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5"
dependencies = [
"windows-sys 0.52.0",
]
[[package]]
name = "http"
version = "0.2.12"
@ -1348,6 +1195,25 @@ version = "1.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "44feda355f4159a7c757171a77de25daf6411e217b4cabd03bd6650690468126"
[[package]]
name = "include_dir"
version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "923d117408f1e49d914f1a379a309cffe4f18c05cf4e3d12e613a15fc81bd0dd"
dependencies = [
"include_dir_macros",
]
[[package]]
name = "include_dir_macros"
version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7cab85a7ed0bd5f0e76d93846e0147172bed2e2d3f859bcc33a8d9699cad1a75"
dependencies = [
"proc-macro2",
"quote",
]
[[package]]
name = "indexmap"
version = "2.2.6"
@ -1419,9 +1285,6 @@ name = "lazy_static"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
dependencies = [
"spin 0.5.2",
]
[[package]]
name = "lebe"
@ -1468,15 +1331,15 @@ version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d"
dependencies = [
"bitflags 2.5.0",
"bitflags 2.6.0",
"libc",
]
[[package]]
name = "libsqlite3-sys"
version = "0.28.0"
version = "0.30.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c10584274047cb335c23d3e61bcef8e323adae7c5c8c760540f73610177fc3f"
checksum = "2e99fb7a497b1e3339bc746195567ed8d3e24945ecd636e3619d20b9de9e9149"
dependencies = [
"cc",
"pkg-config",
@ -1534,16 +1397,6 @@ dependencies = [
"rayon",
]
[[package]]
name = "md-5"
version = "0.10.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf"
dependencies = [
"cfg-if",
"digest",
]
[[package]]
name = "memchr"
version = "2.7.4"
@ -1669,23 +1522,6 @@ dependencies = [
"num-traits",
]
[[package]]
name = "num-bigint-dig"
version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc84195820f291c7697304f3cbdadd1cb7199c0efc917ff5eafd71225c136151"
dependencies = [
"byteorder",
"lazy_static",
"libm",
"num-integer",
"num-iter",
"num-traits",
"rand",
"smallvec",
"zeroize",
]
[[package]]
name = "num-complex"
version = "0.4.6"
@ -1799,12 +1635,6 @@ dependencies = [
"ttf-parser 0.24.1",
]
[[package]]
name = "parking"
version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bb813b8af86854136c6922af0598d719255ecb2179515e6e7730d468f05c9cae"
[[package]]
name = "parking_lot"
version = "0.12.3"
@ -1823,7 +1653,7 @@ checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8"
dependencies = [
"cfg-if",
"libc",
"redox_syscall 0.5.2",
"redox_syscall",
"smallvec",
"windows-targets 0.52.5",
]
@ -1853,15 +1683,6 @@ dependencies = [
"rustc_version",
]
[[package]]
name = "pem-rfc7468"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412"
dependencies = [
"base64ct",
]
[[package]]
name = "percent-encoding"
version = "2.3.1"
@ -1880,27 +1701,6 @@ version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
[[package]]
name = "pkcs1"
version = "0.7.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f"
dependencies = [
"der",
"pkcs8",
"spki",
]
[[package]]
name = "pkcs8"
version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7"
dependencies = [
"der",
"spki",
]
[[package]]
name = "pkg-config"
version = "0.3.30"
@ -2037,7 +1837,7 @@ version = "0.9.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "57206b407293d2bcd3af849ce869d52068623f19e1b5ff8e8778e3309439682b"
dependencies = [
"bitflags 2.5.0",
"bitflags 2.6.0",
"memchr",
"unicase",
]
@ -2066,6 +1866,28 @@ dependencies = [
"proc-macro2",
]
[[package]]
name = "r2d2"
version = "0.8.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "51de85fb3fb6524929c8a2eb85e6b6d363de4e8c48f9e2c2eac4944abc181c93"
dependencies = [
"log",
"parking_lot",
"scheduled-thread-pool",
]
[[package]]
name = "r2d2_sqlite"
version = "0.25.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eb14dba8247a6a15b7fdbc7d389e2e6f03ee9f184f87117706d509c092dfe846"
dependencies = [
"r2d2",
"rusqlite",
"uuid",
]
[[package]]
name = "rand"
version = "0.8.5"
@ -2182,22 +2004,13 @@ dependencies = [
"crossbeam-utils",
]
[[package]]
name = "redox_syscall"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa"
dependencies = [
"bitflags 1.3.2",
]
[[package]]
name = "redox_syscall"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c82cf8cff14456045f55ec4241383baeff27af886adb72ffb2162f99911de0fd"
dependencies = [
"bitflags 2.5.0",
"bitflags 2.6.0",
]
[[package]]
@ -2303,29 +2116,35 @@ dependencies = [
"cfg-if",
"getrandom",
"libc",
"spin 0.9.8",
"spin",
"untrusted",
"windows-sys 0.52.0",
]
[[package]]
name = "rsa"
version = "0.9.6"
name = "rusqlite"
version = "0.32.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5d0e5124fcb30e76a7e79bfee683a2746db83784b86289f6251b54b7950a0dfc"
checksum = "7753b721174eb8ff87a9a0e799e2d7bc3749323e773db92e0984debb00019d6e"
dependencies = [
"const-oid",
"digest",
"num-bigint-dig",
"num-integer",
"num-traits",
"pkcs1",
"pkcs8",
"rand_core",
"signature",
"spki",
"subtle",
"zeroize",
"bitflags 2.6.0",
"chrono",
"fallible-iterator",
"fallible-streaming-iterator",
"hashlink",
"libsqlite3-sys",
"smallvec",
]
[[package]]
name = "rusqlite_migration"
version = "1.3.0-alpha-without-tokio.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7756e1e9f1fb9574c499e66d9e3272e0440fc0ed109f425036e2a6e95e2ad818"
dependencies = [
"include_dir",
"log",
"rusqlite",
]
[[package]]
@ -2349,7 +2168,7 @@ version = "0.38.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f"
dependencies = [
"bitflags 2.5.0",
"bitflags 2.6.0",
"errno",
"libc",
"linux-raw-sys",
@ -2442,6 +2261,15 @@ dependencies = [
"winapi-util",
]
[[package]]
name = "scheduled-thread-pool"
version = "0.2.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3cbc66816425a074528352f5789333ecff06ca41b36b0b0efdfbb29edc391a19"
dependencies = [
"parking_lot",
]
[[package]]
name = "scopeguard"
version = "1.2.0"
@ -2547,7 +2375,7 @@ dependencies = [
"arrayvec",
"async-trait",
"base64 0.22.1",
"bitflags 2.5.0",
"bitflags 2.6.0",
"bytes",
"chrono",
"dashmap",
@ -2582,17 +2410,6 @@ dependencies = [
"digest",
]
[[package]]
name = "sha2"
version = "0.10.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8"
dependencies = [
"cfg-if",
"cpufeatures",
"digest",
]
[[package]]
name = "shimmeringmoon"
version = "0.1.0"
@ -2602,22 +2419,22 @@ dependencies = [
"hypertesseract",
"image 0.25.2",
"imageproc",
"include_dir",
"num",
"plotters",
"poise",
"sqlx",
"r2d2",
"r2d2_sqlite",
"rusqlite",
"rusqlite_migration",
"tokio",
]
[[package]]
name = "signature"
version = "2.2.0"
name = "shlex"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de"
dependencies = [
"digest",
"rand_core",
]
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
[[package]]
name = "simba"
@ -2676,9 +2493,6 @@ name = "smallvec"
version = "1.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67"
dependencies = [
"serde",
]
[[package]]
name = "socket2"
@ -2690,12 +2504,6 @@ dependencies = [
"windows-sys 0.52.0",
]
[[package]]
name = "spin"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d"
[[package]]
name = "spin"
version = "0.9.8"
@ -2705,235 +2513,6 @@ dependencies = [
"lock_api",
]
[[package]]
name = "spki"
version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d"
dependencies = [
"base64ct",
"der",
]
[[package]]
name = "sqlformat"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f895e3734318cc55f1fe66258926c9b910c124d47520339efecbb6c59cec7c1f"
dependencies = [
"nom",
"unicode_categories",
]
[[package]]
name = "sqlx"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "27144619c6e5802f1380337a209d2ac1c431002dd74c6e60aebff3c506dc4f0c"
dependencies = [
"sqlx-core",
"sqlx-macros",
"sqlx-mysql",
"sqlx-postgres",
"sqlx-sqlite",
]
[[package]]
name = "sqlx-core"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a999083c1af5b5d6c071d34a708a19ba3e02106ad82ef7bbd69f5e48266b613b"
dependencies = [
"atoi",
"byteorder",
"bytes",
"chrono",
"crc",
"crossbeam-queue",
"either",
"event-listener",
"futures-channel",
"futures-core",
"futures-intrusive",
"futures-io",
"futures-util",
"hashbrown",
"hashlink",
"hex",
"indexmap",
"log",
"memchr",
"once_cell",
"paste",
"percent-encoding",
"serde",
"serde_json",
"sha2",
"smallvec",
"sqlformat",
"thiserror",
"tokio",
"tokio-stream",
"tracing",
"url",
]
[[package]]
name = "sqlx-macros"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a23217eb7d86c584b8cbe0337b9eacf12ab76fe7673c513141ec42565698bb88"
dependencies = [
"proc-macro2",
"quote",
"sqlx-core",
"sqlx-macros-core",
"syn 2.0.66",
]
[[package]]
name = "sqlx-macros-core"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a099220ae541c5db479c6424bdf1b200987934033c2584f79a0e1693601e776"
dependencies = [
"dotenvy",
"either",
"heck",
"hex",
"once_cell",
"proc-macro2",
"quote",
"serde",
"serde_json",
"sha2",
"sqlx-core",
"sqlx-mysql",
"sqlx-postgres",
"sqlx-sqlite",
"syn 2.0.66",
"tempfile",
"tokio",
"url",
]
[[package]]
name = "sqlx-mysql"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5afe4c38a9b417b6a9a5eeffe7235d0a106716495536e7727d1c7f4b1ff3eba6"
dependencies = [
"atoi",
"base64 0.22.1",
"bitflags 2.5.0",
"byteorder",
"bytes",
"chrono",
"crc",
"digest",
"dotenvy",
"either",
"futures-channel",
"futures-core",
"futures-io",
"futures-util",
"generic-array",
"hex",
"hkdf",
"hmac",
"itoa",
"log",
"md-5",
"memchr",
"once_cell",
"percent-encoding",
"rand",
"rsa",
"serde",
"sha1",
"sha2",
"smallvec",
"sqlx-core",
"stringprep",
"thiserror",
"tracing",
"whoami",
]
[[package]]
name = "sqlx-postgres"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b1dbb157e65f10dbe01f729339c06d239120221c9ad9fa0ba8408c4cc18ecf21"
dependencies = [
"atoi",
"base64 0.22.1",
"bitflags 2.5.0",
"byteorder",
"chrono",
"crc",
"dotenvy",
"etcetera",
"futures-channel",
"futures-core",
"futures-io",
"futures-util",
"hex",
"hkdf",
"hmac",
"home",
"itoa",
"log",
"md-5",
"memchr",
"once_cell",
"rand",
"serde",
"serde_json",
"sha2",
"smallvec",
"sqlx-core",
"stringprep",
"thiserror",
"tracing",
"whoami",
]
[[package]]
name = "sqlx-sqlite"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b2cdd83c008a622d94499c0006d8ee5f821f36c89b7d625c900e5dc30b5c5ee"
dependencies = [
"atoi",
"chrono",
"flume",
"futures-channel",
"futures-core",
"futures-executor",
"futures-intrusive",
"futures-util",
"libsqlite3-sys",
"log",
"percent-encoding",
"serde",
"serde_urlencoded",
"sqlx-core",
"tracing",
"url",
]
[[package]]
name = "stringprep"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7b4df3d392d81bd458a8a621b8bffbd2302a12ffe288a9d931670948749463b1"
dependencies = [
"unicode-bidi",
"unicode-normalization",
"unicode-properties",
]
[[package]]
name = "strsim"
version = "0.11.1"
@ -3176,17 +2755,6 @@ dependencies = [
"tokio",
]
[[package]]
name = "tokio-stream"
version = "0.1.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "267ac89e0bec6e691e5813911606935d77c476ff49024f98abcea3e7b15e37af"
dependencies = [
"futures-core",
"pin-project-lite",
"tokio",
]
[[package]]
name = "tokio-tungstenite"
version = "0.21.0"
@ -3404,18 +2972,6 @@ dependencies = [
"tinyvec",
]
[[package]]
name = "unicode-properties"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e4259d9d4425d9f0661581b804cb85fe66a4c631cadd8f490d1c13a35d5d9291"
[[package]]
name = "unicode_categories"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e"
[[package]]
name = "untrusted"
version = "0.9.0"
@ -3440,6 +2996,16 @@ version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9"
[[package]]
name = "uuid"
version = "1.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "81dfa00651efa65069b0b6b651f4aaa31ba9e3c3ce0137aaad053604ee7e0314"
dependencies = [
"getrandom",
"rand",
]
[[package]]
name = "v_frame"
version = "0.3.8"
@ -3494,12 +3060,6 @@ version = "0.11.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
[[package]]
name = "wasite"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b"
[[package]]
name = "wasm-bindgen"
version = "0.2.92"
@ -3610,16 +3170,6 @@ version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "53a85b86a771b1c87058196170769dd264f66c0782acf1ae6cc51bfd64b39082"
[[package]]
name = "whoami"
version = "1.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a44ab49fad634e88f55bf8f9bb3abd2f27d7204172a112c7c9987e01c1c94ea9"
dependencies = [
"redox_syscall 0.4.1",
"wasite",
]
[[package]]
name = "wide"
version = "0.7.26"

View file

@ -10,10 +10,14 @@ image = "0.25.2"
num = "0.4.3"
plotters = { git="https://github.com/starlitcanopy/plotters.git", rev="986cd959362a2dbec8d1b25670fd083b904d7b8c", features=["bitmap_backend"] }
poise = "0.6.1"
sqlx = { version = "0.8.0", features = ["sqlite", "runtime-tokio", "chrono"] }
hypertesseract = { features=["image"], git="https://github.com/BlueGhostGH/hypertesseract.git", rev="4e05063" }
tokio = {version="1.38.0", features=["rt-multi-thread"]}
imageproc = "0.25.0"
rusqlite = { version = "0.32.1", features = ["bundled", "chrono"] }
r2d2_sqlite = "0.25.0"
r2d2 = "0.8.10"
rusqlite_migration = {version="1.3.0-alpha-without-tokio.1", features = ["from-directory"]}
include_dir = "0.7.4"
[profile.dev.package."*"]
opt-level = 3

View file

@ -0,0 +1,36 @@
-- {{{ users
create table IF NOT EXISTS users (
id INTEGER NOT NULL PRIMARY KEY,
discord_id TEXT UNIQUE NOT NULL,
is_pookie BOOL NOT NULL DEFAULT 0
);
-- }}}
-- {{{ plays
CREATE TABLE IF NOT EXISTS plays (
id INTEGER NOT NULL PRIMARY KEY,
chart_id INTEGER NOT NULL,
user_id INTEGER NOT NULL,
discord_attachment_id TEXT,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
max_recall INTEGER,
far_notes INTEGER,
FOREIGN KEY (chart_id) REFERENCES charts(id),
FOREIGN KEY (user_id) REFERENCES users(id)
);
-- }}}
-- {{{ scores
CREATE TABLE IF NOT EXISTS scores (
id INTEGER NOT NULL PRIMARY KEY,
play_id INTEGER NOT NULL,
score INTEGER NOT NULL,
creation_ptt INTEGER,
scoring_system TEXT NOT NULL CHECK (scoring_system IN ('standard', 'sdf', 'ex')),
FOREIGN KEY (play_id) REFERENCES plays(id),
UNIQUE(play_id, scoring_system)
)
-- }}}

View file

@ -0,0 +1,29 @@
-- {{{ songs
CREATE TABLE IF NOT EXISTS songs (
id INTEGER NOT NULL PRIMARY KEY,
title TEXT NOT NULL,
artist TEXT NOT NULL,
side TEXT NOT NULL CHECK (side IN ('light', 'conflict', 'silent')),
bpm TEXT NOT NULL,
pack TEXT,
UNIQUE(title, artist)
);
-- }}}
-- {{{ charts
CREATE TABLE IF NOT EXISTS charts (
id INTEGER NOT NULL PRIMARY KEY,
song_id INTEGER NOT NULL,
note_design TEXT,
shorthand TEXT,
difficulty TEXT NOT NULL CHECK (difficulty IN ('PST','PRS','FTR','ETR','BYD')),
level TEXT NOT NULL,
note_count INTEGER NOT NULL,
chart_constant INTEGER NOT NULL,
FOREIGN KEY (song_id) REFERENCES songs(id),
UNIQUE(song_id, difficulty)
);
-- }}}

View file

@ -0,0 +1,29 @@
-- {{{ plays
CREATE TABLE IF NOT EXISTS plays (
id INTEGER NOT NULL PRIMARY KEY,
chart_id INTEGER NOT NULL,
user_id INTEGER NOT NULL,
discord_attachment_id TEXT,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
max_recall INTEGER,
far_notes INTEGER,
FOREIGN KEY (chart_id) REFERENCES charts(id),
FOREIGN KEY (user_id) REFERENCES users(id)
);
-- }}}
-- {{{ scores
CREATE TABLE IF NOT EXISTS scores (
id INTEGER NOT NULL PRIMARY KEY,
play_id INTEGER NOT NULL,
score INTEGER NOT NULL,
creation_ptt INTEGER,
scoring_system TEXT NOT NULL CHECK (scoring_system IN ('standard', 'sdf', 'ex')),
FOREIGN KEY (play_id) REFERENCES plays(id),
UNIQUE(play_id, scoring_system)
)
-- }}}

View file

@ -1,67 +0,0 @@
# {{{ users
create table IF NOT EXISTS users (
id INTEGER NOT NULL PRIMARY KEY,
discord_id TEXT UNIQUE NOT NULL,
is_pookie BOOL NOT NULL DEFAULT 0
);
# }}}
# {{{ songs
CREATE TABLE IF NOT EXISTS songs (
id INTEGER NOT NULL PRIMARY KEY,
title TEXT NOT NULL,
artist TEXT NOT NULL,
side TEXT NOT NULL CHECK (side IN ('light', 'conflict', 'silent')),
bpm TEXT NOT NULL,
pack TEXT,
UNIQUE(title, artist)
);
# }}}
# {{{ charts
CREATE TABLE IF NOT EXISTS charts (
id INTEGER NOT NULL PRIMARY KEY,
song_id INTEGER NOT NULL,
note_design TEXT,
shorthand TEXT,
difficulty TEXT NOT NULL CHECK (difficulty IN ('PST','PRS','FTR','ETR','BYD')),
level TEXT NOT NULL,
note_count INTEGER NOT NULL,
chart_constant INTEGER NOT NULL,
FOREIGN KEY (song_id) REFERENCES songs(id),
UNIQUE(song_id, difficulty)
);
# }}}
# {{{ plays
CREATE TABLE IF NOT EXISTS plays (
id INTEGER NOT NULL PRIMARY KEY,
chart_id INTEGER NOT NULL,
user_id INTEGER NOT NULL,
discord_attachment_id TEXT,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
max_recall INTEGER,
far_notes INTEGER,
FOREIGN KEY (chart_id) REFERENCES charts(id),
FOREIGN KEY (user_id) REFERENCES users(id)
);
# }}}
# {{{ scores
CREATE TABLE IF NOT EXISTS scores (
id INTEGER NOT NULL PRIMARY KEY,
play_id INTEGER NOT NULL,
score INTEGER NOT NULL,
creation_ptt INTEGER,
scoring_system NOT NULL CHECK (scoring_system IN ('standard', 'sdf', 'ex')),
FOREIGN KEY (play_id) REFERENCES plays(id),
UNIQUE(play_id, scoring_system)
)
# }}}
insert into users(discord_id) values (385759924917108740);

View file

@ -1,5 +1,4 @@
use image::RgbaImage;
use sqlx::query;
use crate::{
assets::get_data_dir,
@ -122,16 +121,8 @@ impl GoalStats {
user: &User,
scoring_system: ScoringSystem,
) -> Result<Self, Error> {
let plays = get_best_plays(
&ctx.db,
&ctx.song_cache,
user.id,
scoring_system,
0,
usize::MAX,
None,
)
.await??;
let plays = get_best_plays(ctx, user.id, scoring_system, 0, usize::MAX, None)??;
let conn = ctx.db.get()?;
// {{{ PM count
let pm_count = plays
@ -142,30 +133,31 @@ impl GoalStats {
.count();
// }}}
// {{{ Play count
let play_count = query!(
"SELECT count() as count FROM plays WHERE user_id=?",
user.id
)
.fetch_one(&ctx.db)
.await?
.count as usize;
let play_count = conn
.prepare_cached("SELECT count() as count FROM plays WHERE user_id=?")?
.query_row([user.id], |row| row.get(0))?;
// }}}
// {{{ Peak ptt
let peak_ptt = query!(
let peak_ptt = conn
.prepare_cached(
"
SELECT s.creation_ptt
FROM plays p
JOIN scores s ON s.play_id = p.id
WHERE user_id = ?
AND scoring_system = ?
ORDER BY s.creation_ptt DESC
LIMIT 1
",
)?
.query_row(
(
user.id,
ScoringSystem::SCORING_SYSTEM_DB_STRINGS[scoring_system.to_index()]
ScoringSystem::SCORING_SYSTEM_DB_STRINGS[scoring_system.to_index()],
),
|row| row.get(0),
)
.fetch_one(&ctx.db)
.await?
.creation_ptt
.ok_or_else(|| "No ptt history data found")? as u32;
.map_err(|_| "No ptt history data found")?;
// }}}
// {{{ Peak PM relay
let peak_pm_relay = {

View file

@ -1,12 +1,15 @@
use std::{fmt::Display, num::NonZeroU16, path::PathBuf};
use image::{ImageBuffer, Rgb};
use sqlx::SqlitePool;
use rusqlite::types::{FromSql, FromSqlError, FromSqlResult, ValueRef};
use crate::{bitmap::Color, context::Error};
use crate::{
bitmap::Color,
context::{DbConnection, Error},
};
// {{{ Difficuly
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, sqlx::Type)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum Difficulty {
PST,
PRS,
@ -29,17 +32,19 @@ impl Difficulty {
}
}
impl TryFrom<String> for Difficulty {
type Error = String;
impl FromSql for Difficulty {
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
let str: String = rusqlite::types::FromSql::column_result(value)?;
fn try_from(value: String) -> Result<Self, Self::Error> {
for (i, s) in Self::DIFFICULTY_SHORTHANDS.iter().enumerate() {
if value == **s {
if str == **s {
return Ok(Self::DIFFICULTIES[i]);
}
}
Err(format!("Cannot convert {} to difficulty", value))
FromSqlResult::Err(FromSqlError::Other(
format!("Cannot convert {} to difficulty", str).into(),
))
}
}
@ -120,17 +125,19 @@ impl Display for Level {
}
}
impl TryFrom<String> for Level {
type Error = String;
impl FromSql for Level {
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
let str: String = rusqlite::types::FromSql::column_result(value)?;
fn try_from(value: String) -> Result<Self, Self::Error> {
for (i, s) in Self::LEVEL_STRINGS.iter().enumerate() {
if value == **s {
if str == **s {
return Ok(Self::LEVELS[i]);
}
}
Err(format!("Cannot convert {} to a level", value))
FromSqlResult::Err(FromSqlError::Other(
format!("Cannot convert {} to level", str).into(),
))
}
}
// }}}
@ -152,17 +159,19 @@ impl Side {
}
}
impl TryFrom<String> for Side {
type Error = String;
impl FromSql for Side {
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
let str: String = rusqlite::types::FromSql::column_result(value)?;
fn try_from(value: String) -> Result<Self, Self::Error> {
for (i, s) in Self::SIDE_STRINGS.iter().enumerate() {
if value == **s {
if str == **s {
return Ok(Self::SIDES[i]);
}
}
Err(format!("Cannot convert {} to difficulty", value))
FromSqlResult::Err(FromSqlError::Other(
format!("Cannot convert {} to side", str).into(),
))
}
}
// }}}
@ -304,44 +313,53 @@ impl SongCache {
}
// {{{ Populate cache
pub async fn new(pool: &SqlitePool) -> Result<Self, Error> {
pub fn new(conn: &DbConnection) -> Result<Self, Error> {
let conn = conn.get()?;
let mut result = Self::default();
// {{{ Songs
let songs = sqlx::query!("SELECT * FROM songs").fetch_all(pool).await?;
for song in songs {
let song = Song {
id: song.id as u32,
lowercase_title: song.title.to_lowercase(),
title: song.title,
artist: song.artist,
pack: song.pack,
bpm: song.bpm,
side: Side::try_from(song.side)?,
};
let mut query = conn.prepare_cached("SELECT * FROM songs")?;
let songs = query.query_map((), |row| {
Ok(Song {
id: row.get("id")?,
lowercase_title: row.get::<_, String>("title")?.to_lowercase(),
title: row.get("title")?,
artist: row.get("artist")?,
pack: row.get("pack")?,
bpm: row.get("bpm")?,
side: row.get("side")?,
})
})?;
for song in songs {
let song = song?;
let song_id = song.id as usize;
if song_id >= result.songs.len() {
result.songs.resize(song_id + 1, None);
}
result.songs[song_id] = Some(CachedSong::new(song));
}
// }}}
// {{{ Charts
let charts = sqlx::query!("SELECT * FROM charts").fetch_all(pool).await?;
for chart in charts {
let chart = Chart {
id: chart.id as u32,
song_id: chart.song_id as u32,
shorthand: chart.shorthand,
difficulty: Difficulty::try_from(chart.difficulty)?,
level: Level::try_from(chart.level)?,
chart_constant: chart.chart_constant as u32,
note_count: chart.note_count as u32,
let mut query = conn.prepare_cached("SELECT * FROM charts")?;
let charts = query.query_map((), |row| {
Ok(Chart {
id: row.get("id")?,
song_id: row.get("song_id")?,
shorthand: row.get("shorthand")?,
difficulty: row.get("difficulty")?,
level: row.get("level")?,
chart_constant: row.get("chart_constant")?,
note_count: row.get("note_count")?,
note_design: row.get("note_design")?,
cached_jacket: None,
note_design: chart.note_design,
};
})
})?;
for chart in charts {
let chart = chart?;
// {{{ Tie chart to song
{

View file

@ -9,14 +9,12 @@ use num::Zero;
use poise::serenity_prelude::{
Attachment, AttachmentId, CreateAttachment, CreateEmbed, CreateEmbedAuthor, Timestamp,
};
use sqlx::query_as;
use sqlx::{query, SqlitePool};
use rusqlite::Row;
use crate::arcaea::chart::{Chart, Song};
use crate::context::{Error, UserContext};
use crate::user::User;
use super::chart::SongCache;
use super::rating::{rating_as_fixed, rating_as_float};
use super::score::{Score, ScoringSystem};
@ -61,11 +59,13 @@ impl CreatePlay {
}
// {{{ Save
pub async fn save(self, ctx: &UserContext, user: &User, chart: &Chart) -> Result<Play, Error> {
pub fn save(self, ctx: &UserContext, user: &User, chart: &Chart) -> Result<Play, Error> {
let conn = ctx.db.get()?;
let attachment_id = self.discord_attachment_id.map(|i| i.get() as i64);
// {{{ Save current data to play
let play = sqlx::query!(
let (id, created_at) = conn
.prepare_cached(
"
INSERT INTO plays(
user_id,chart_id,discord_attachment_id,
@ -74,88 +74,55 @@ impl CreatePlay {
VALUES(?,?,?,?,?)
RETURNING id, created_at
",
)?
.query_row(
(
user.id,
chart.id,
attachment_id,
self.max_recall,
self.far_notes
)
.fetch_one(&ctx.db)
.await?;
self.far_notes,
),
|row| Ok((row.get("id")?, row.get("created_at")?)),
)?;
// }}}
// {{{ Update creation ptt data
let scores = ScoreCollection::from_standard_score(self.score, chart);
for system in ScoringSystem::SCORING_SYSTEMS {
let i = system.to_index();
let plays = get_best_plays(&ctx.db, &ctx.song_cache, user.id, system, 30, 30, None)
.await?
.ok();
let plays = get_best_plays(ctx, user.id, system, 30, 30, None)?.ok();
let creation_ptt: Option<_> = try { rating_as_fixed(compute_b30_ptt(system, &plays?)) };
query!(
conn.prepare_cached(
"
INSERT INTO scores(play_id, score, creation_ptt, scoring_system)
VALUES (?,?,?,?)
",
play.id,
)?
.execute((
id,
scores.0[i].0,
creation_ptt,
ScoringSystem::SCORING_SYSTEM_DB_STRINGS[i]
)
.execute(&ctx.db)
.await?;
ScoringSystem::SCORING_SYSTEM_DB_STRINGS[i],
))?;
}
// }}}
Ok(Play {
id: play.id as u32,
created_at: play.created_at,
id,
created_at,
scores,
chart_id: chart.id,
user_id: user.id,
scores,
max_recall: self.max_recall,
far_notes: self.far_notes,
})
}
// }}}
}
// }}}
// {{{ DbPlay
/// Construct a `Play` from a sqlite return record.
#[macro_export]
macro_rules! play_from_db_record {
($chart:expr, $record:expr) => {{
use crate::arcaea::play::{Play, ScoreCollection};
use crate::arcaea::score::Score;
Play {
id: $record.id as u32,
chart_id: $record.chart_id as u32,
user_id: $record.user_id as u32,
scores: ScoreCollection::from_standard_score(Score($record.score as u32), $chart),
max_recall: $record.max_recall.map(|r| r as u32),
far_notes: $record.far_notes.map(|r| r as u32),
created_at: $record.created_at,
}
}};
}
/// Typed version of the input to the macro above.
/// Useful when using the non-macro version of the sqlx functions.
#[derive(Debug, sqlx::FromRow)]
pub struct DbPlay {
pub id: i64,
pub chart_id: i64,
pub user_id: i64,
pub created_at: chrono::NaiveDateTime,
// Score details
pub max_recall: Option<i64>,
pub far_notes: Option<i64>,
pub score: i64,
}
// }}}
// {{{ Score data
#[derive(Debug, Clone, Copy)]
@ -185,6 +152,20 @@ pub struct Play {
}
impl Play {
// {{{ Row parsing
#[inline]
pub fn from_sql(chart: &Chart, row: &Row) -> Result<Self, rusqlite::Error> {
Ok(Play {
id: row.get("id")?,
chart_id: row.get("chart_id")?,
user_id: row.get("user_id")?,
created_at: row.get("created_at")?,
max_recall: row.get("max_recall")?,
far_notes: row.get("far_notes")?,
scores: ScoreCollection::from_standard_score(Score(row.get("score")?), chart),
})
}
// }}}
// {{{ Query the underlying score
#[inline]
pub fn score(&self, system: ScoringSystem) -> Score {
@ -272,9 +253,9 @@ impl Play {
/// Creates a discord embed for this play.
///
/// The `index` variable is only used to create distinct filenames.
pub async fn to_embed(
pub fn to_embed(
&self,
db: &SqlitePool,
ctx: &UserContext,
user: &User,
song: &Song,
chart: &Chart,
@ -282,7 +263,10 @@ impl Play {
author: Option<&poise::serenity_prelude::User>,
) -> Result<(CreateEmbed, Option<CreateAttachment>), Error> {
// {{{ Get previously best score
let prev_play = query!(
let prev_play = ctx
.db
.get()?
.prepare_cached(
"
SELECT
p.id, p.chart_id, p.user_id, p.created_at,
@ -296,19 +280,11 @@ impl Play {
ORDER BY s.score DESC
LIMIT 1
",
user.id,
chart.id,
self.created_at
)
.fetch_optional(db)
.await
.map_err(|_| {
format!(
"Could not find any scores for {} [{:?}]",
song.title, chart.difficulty
)
})?
.map(|p| play_from_db_record!(chart, p));
)?
.query_row((user.id, chart.id, self.created_at), |row| {
Self::from_sql(chart, row)
})
.ok();
let prev_score = prev_play.as_ref().map(|p| p.score(ScoringSystem::Standard));
let prev_zeta_score = prev_play.as_ref().map(|p| p.score(ScoringSystem::EX));
@ -408,22 +384,23 @@ impl Play {
// {{{ General functions
pub type PlayCollection<'a> = Vec<(Play, &'a Song, &'a Chart)>;
pub async fn get_best_plays<'a>(
db: &SqlitePool,
song_cache: &'a SongCache,
pub fn get_best_plays<'a>(
ctx: &'a UserContext,
user_id: u32,
scoring_system: ScoringSystem,
min_amount: usize,
max_amount: usize,
before: Option<NaiveDateTime>,
) -> Result<Result<PlayCollection<'a>, String>, Error> {
let conn = ctx.db.get()?;
// {{{ DB data fetching
let plays: Vec<DbPlay> = query_as(
let mut plays = conn
.prepare_cached(
"
SELECT
p.id, p.chart_id, p.user_id, p.created_at,
p.max_recall, p.far_notes, s.score,
MAX(s.score) as _cscore
MAX(cs.score) as _cscore
-- ^ This is only here to make sqlite pick the correct row for the bare columns
FROM plays p
JOIN scores s ON s.play_id = p.id
@ -434,12 +411,20 @@ pub async fn get_best_plays<'a>(
AND p.created_at<=?
GROUP BY p.chart_id
",
)
.bind(ScoringSystem::SCORING_SYSTEM_DB_STRINGS[scoring_system.to_index()])
.bind(user_id)
.bind(before.unwrap_or_else(|| Utc::now().naive_utc()))
.fetch_all(db)
.await?;
)?
.query_and_then(
(
ScoringSystem::SCORING_SYSTEM_DB_STRINGS[scoring_system.to_index()],
user_id,
before.unwrap_or_else(|| Utc::now().naive_utc()),
),
|row| {
let (song, chart) = ctx.song_cache.lookup_chart(row.get("chart_id")?)?;
let play = Play::from_sql(chart, row)?;
Ok((play, song, chart))
},
)?
.collect::<Result<Vec<_>, Error>>()?;
// }}}
if plays.len() < min_amount {
@ -450,17 +435,6 @@ pub async fn get_best_plays<'a>(
}
// {{{ B30 computation
// NOTE: we reallocate here, although we do not have much of a choice,
// unless we want to be lazy about things
let mut plays: Vec<(Play, &Song, &Chart)> = plays
.into_iter()
.map(|play| {
let (song, chart) = song_cache.lookup_chart(play.chart_id as u32)?;
let play = play_from_db_record!(chart, play);
Ok((play, song, chart))
})
.collect::<Result<Vec<_>, Error>>()?;
plays.sort_by_key(|(play, _, chart)| -play.play_rating(scoring_system, chart.chart_constant));
plays.truncate(max_amount);
// }}}
@ -480,7 +454,8 @@ pub fn compute_b30_ptt(scoring_system: ScoringSystem, plays: &PlayCollection<'_>
// }}}
// {{{ Maintenance functions
pub async fn generate_missing_scores(ctx: &UserContext) -> Result<(), Error> {
let plays = query!(
let conn = ctx.db.get()?;
let mut query = conn.prepare_cached(
"
SELECT
p.id, p.chart_id, p.user_id, p.created_at,
@ -489,36 +464,28 @@ pub async fn generate_missing_scores(ctx: &UserContext) -> Result<(), Error> {
JOIN scores s ON s.play_id = p.id
WHERE s.scoring_system='standard'
ORDER BY p.created_at ASC
"
)
// Can't use the stream based version because of db locking...
.fetch_all(&ctx.db)
.await?;
",
)?;
let plays = query.query_and_then((), |row| -> Result<_, Error> {
let (_, chart) = ctx.song_cache.lookup_chart(row.get("chart_id")?)?;
let play = Play::from_sql(chart, row)?;
Ok(play)
})?;
let mut i = 0;
for play in plays {
let (_, chart) = ctx.song_cache.lookup_chart(play.chart_id as u32)?;
let play = play_from_db_record!(chart, play);
let play = play?;
for system in ScoringSystem::SCORING_SYSTEMS {
let i = system.to_index();
let plays = get_best_plays(
&ctx.db,
&ctx.song_cache,
play.user_id,
system,
30,
30,
Some(play.created_at),
)
.await?
.ok();
let plays =
get_best_plays(&ctx, play.user_id, system, 30, 30, Some(play.created_at))?.ok();
let creation_ptt: Option<_> = try { rating_as_fixed(compute_b30_ptt(system, &plays?)) };
let raw_score = play.scores.0[i].0;
query!(
conn.prepare_cached(
"
INSERT INTO scores(play_id, score, creation_ptt, scoring_system)
VALUES ($1, $2, $3, $4)
@ -527,15 +494,14 @@ pub async fn generate_missing_scores(ctx: &UserContext) -> Result<(), Error> {
score=$2, creation_ptt=$3
WHERE play_id = $1
AND scoring_system = $4
",
)?
.execute((
play.id,
raw_score,
creation_ptt,
ScoringSystem::SCORING_SYSTEM_DB_STRINGS[i],
)
.execute(&ctx.db)
.await?;
))?;
}
i += 1;

View file

@ -1,10 +1,9 @@
use poise::serenity_prelude::{CreateAttachment, CreateEmbed, CreateMessage};
use sqlx::query;
use crate::{
arcaea::chart::Side,
arcaea::{chart::Side, play::Play},
context::{Context, Error},
get_user, play_from_db_record,
get_user,
recognition::fuzzy_song_name::guess_song_and_chart,
};
use std::io::Cursor;
@ -23,7 +22,7 @@ use poise::CreateReply;
use crate::{
arcaea::score::{Score, ScoringSystem},
user::discord_it_to_discord_user,
user::discord_id_to_discord_user,
};
// {{{ Top command
@ -55,16 +54,18 @@ async fn info(
None => None,
};
let play_count = query!(
let play_count: usize = ctx
.data()
.db
.get()?
.prepare_cached(
"
SELECT COUNT(*) as count
FROM plays
WHERE chart_id=?
",
chart.id
)
.fetch_one(&ctx.data().db)
.await?;
)?
.query_row([chart.id], |row| row.get(0))?;
let mut embed = CreateEmbed::default()
.title(format!(
@ -77,7 +78,7 @@ async fn info(
format!("{:.1}", chart.chart_constant as f32 / 100.0),
true,
)
.field("Total plays", format!("{}", play_count.count), true)
.field("Total plays", format!("{play_count}"), true)
.field("BPM", &song.bpm, true)
.field("Side", Side::SIDE_STRINGS[song.side.to_index()], true)
.field("Artist", &song.title, true);
@ -117,7 +118,11 @@ async fn best(
let user = get_user!(&ctx);
let (song, chart) = guess_song_and_chart(&ctx.data(), &name)?;
let play = query!(
let play = ctx
.data()
.db
.get()?
.prepare_cached(
"
SELECT
p.id, p.chart_id, p.user_id, p.created_at,
@ -130,29 +135,23 @@ async fn best(
ORDER BY s.score DESC
LIMIT 1
",
user.id,
chart.id
)
.fetch_one(&ctx.data().db)
.await
)?
.query_row((user.id, chart.id), |row| Play::from_sql(chart, row))
.map_err(|_| {
format!(
"Could not find any scores for {} [{:?}]",
song.title, chart.difficulty
)
})?;
let play = play_from_db_record!(chart, play);
let (embed, attachment) = play
.to_embed(
&ctx.data().db,
let (embed, attachment) = play.to_embed(
ctx.data(),
&user,
&song,
&chart,
song,
chart,
0,
Some(&discord_it_to_discord_user(&ctx, &user.discord_id).await?),
)
.await?;
Some(&discord_id_to_discord_user(&ctx, &user.discord_id).await?),
)?;
ctx.channel_id()
.send_files(ctx.http(), attachment, CreateMessage::new().embed(embed))
@ -177,7 +176,11 @@ async fn plot(
let (song, chart) = guess_song_and_chart(&ctx.data(), &name)?;
// SAFETY: we limit the amount of plotted plays to 1000.
let plays = query!(
let plays = ctx
.data()
.db
.get()?
.prepare_cached(
"
SELECT
p.id, p.chart_id, p.user_id, p.created_at,
@ -190,11 +193,9 @@ async fn plot(
ORDER BY s.score DESC
LIMIT 1000
",
user.id,
chart.id
)
.fetch_all(&ctx.data().db)
.await?;
)?
.query_map((user.id, chart.id), |row| Play::from_sql(chart, row))?
.collect::<Result<Vec<_>, _>>()?;
if plays.len() == 0 {
ctx.reply(format!(
@ -209,7 +210,7 @@ async fn plot(
let max_time = plays.iter().map(|p| p.created_at).max().unwrap();
let mut min_score = plays
.iter()
.map(|p| play_from_db_record!(chart, p).score(scoring_system))
.map(|p| p.score(scoring_system))
.min()
.unwrap()
.0 as i64;
@ -266,7 +267,7 @@ async fn plot(
.map(|play| {
(
play.created_at.and_utc().timestamp_millis(),
play_from_db_record!(chart, play).score(scoring_system),
play.score(scoring_system),
)
})
.collect();

View file

@ -1,16 +1,15 @@
use std::time::Instant;
use crate::arcaea::play::CreatePlay;
use crate::arcaea::play::{CreatePlay, Play};
use crate::arcaea::score::Score;
use crate::context::{Context, Error};
use crate::recognition::recognize::{ImageAnalyzer, ScoreKind};
use crate::user::{discord_it_to_discord_user, User};
use crate::{edit_reply, get_user, play_from_db_record, timed};
use crate::user::{discord_id_to_discord_user, User};
use crate::{edit_reply, get_user, timed};
use image::DynamicImage;
use poise::serenity_prelude::futures::future::join_all;
use poise::serenity_prelude::CreateMessage;
use poise::{serenity_prelude as serenity, CreateReply};
use sqlx::query;
// {{{ Score
/// Score management
@ -121,15 +120,13 @@ pub async fn magic(
.with_attachment(file)
.with_fars(maybe_fars)
.with_max_recall(max_recall)
.save(&ctx.data(), &user, &chart)
.await?;
.save(&ctx.data(), &user, &chart)?;
// }}}
// }}}
// {{{ Deliver embed
let (embed, attachment) = timed!("to embed", {
play.to_embed(&ctx.data().db, &user, &song, &chart, i, None)
.await?
play.to_embed(ctx.data(), &user, &song, &chart, i, None)?
});
embeds.push(embed);
@ -183,11 +180,14 @@ pub async fn delete(
let mut count = 0;
for id in ids {
let res = query!("DELETE FROM plays WHERE id=? AND user_id=?", id, user.id)
.execute(&ctx.data().db)
.await?;
let res = ctx
.data()
.db
.get()?
.prepare_cached("DELETE FROM plays WHERE id=? AND user_id=?")?
.execute((id, user.id))?;
if res.rows_affected() == 0 {
if res == 0 {
ctx.reply(format!("No play with id {} found", id)).await?;
} else {
count += 1;
@ -216,8 +216,10 @@ pub async fn show(
let mut embeds = Vec::with_capacity(ids.len());
let mut attachments = Vec::with_capacity(ids.len());
let conn = ctx.data().db.get()?;
for (i, id) in ids.iter().enumerate() {
let res = query!(
let (song, chart, play, discord_id) = conn
.prepare_cached(
"
SELECT
p.id, p.chart_id, p.user_id, p.created_at,
@ -231,21 +233,21 @@ pub async fn show(
ORDER BY s.score DESC
LIMIT 1
",
id
)
.fetch_one(&ctx.data().db)
.await
.map_err(|_| format!("Could not find play with id {}", id))?;
)?
.query_and_then([id], |row| -> Result<_, Error> {
let (song, chart) = ctx.data().song_cache.lookup_chart(row.get("chart_id")?)?;
let play = Play::from_sql(chart, row)?;
let discord_id = row.get::<_, String>("discord_id")?;
Ok((song, chart, play, discord_id))
})?
.next()
.ok_or_else(|| format!("Could not find play with id {}", id))??;
let (song, chart) = ctx.data().song_cache.lookup_chart(res.chart_id as u32)?;
let play = play_from_db_record!(chart, res);
let author = discord_id_to_discord_user(&ctx, &discord_id).await?;
let user = User::by_id(ctx.data(), play.user_id)?;
let author = discord_it_to_discord_user(&ctx, &res.discord_id).await?;
let user = User::by_id(&ctx.data().db, play.user_id).await?;
let (embed, attachment) = play
.to_embed(&ctx.data().db, &user, song, chart, i, Some(&author))
.await?;
let (embed, attachment) =
play.to_embed(ctx.data(), &user, song, chart, i, Some(&author))?;
embeds.push(embed);
attachments.extend(attachment);

View file

@ -5,7 +5,6 @@ use poise::{
serenity_prelude::{CreateAttachment, CreateEmbed},
CreateReply,
};
use sqlx::query;
use crate::{
arcaea::{
@ -26,7 +25,7 @@ use crate::{
context::{Context, Error},
get_user,
logs::debug_image_log,
reply_errors,
reply_errors, timed,
user::User,
};
@ -53,9 +52,9 @@ async fn best_plays(
let user_ctx = ctx.data();
let plays = reply_errors!(
ctx,
timed!("get_best_plays", {
get_best_plays(
&user_ctx.db,
&user_ctx.song_cache,
user_ctx,
user.id,
scoring_system,
if require_full {
@ -64,9 +63,9 @@ async fn best_plays(
grid_size.0 * (grid_size.1.max(1) - 1) + 1
} as usize,
(grid_size.0 * grid_size.1) as usize,
None
)
.await?
None,
)?
})
);
// {{{ Layout
@ -463,48 +462,42 @@ pub async fn bany(
#[poise::command(prefix_command, slash_command, user_cooldown = 1)]
async fn meta(ctx: Context<'_>) -> Result<(), Error> {
let user = get_user!(&ctx);
let song_count = query!("SELECT count() as count FROM songs")
.fetch_one(&ctx.data().db)
.await?
.count;
let conn = ctx.data().db.get()?;
let song_count: usize = conn
.prepare_cached("SELECT count() as count FROM songs")?
.query_row((), |row| row.get(0))?;
let chart_count = query!("SELECT count() as count FROM charts")
.fetch_one(&ctx.data().db)
.await?
.count;
let chart_count: usize = conn
.prepare_cached("SELECT count() as count FROM charts")?
.query_row((), |row| row.get(0))?;
let users_count = query!("SELECT count() as count FROM users")
.fetch_one(&ctx.data().db)
.await?
.count;
let users_count: usize = conn
.prepare_cached("SELECT count() as count FROM users")?
.query_row((), |row| row.get(0))?;
let pookie_count = query!(
let pookie_count: usize = conn
.prepare_cached(
"
SELECT count() as count
FROM users
WHERE is_pookie=1
"
)
.fetch_one(&ctx.data().db)
.await?
.count;
",
)?
.query_row((), |row| row.get(0))?;
let play_count = query!("SELECT count() as count FROM plays")
.fetch_one(&ctx.data().db)
.await?
.count;
let play_count: usize = conn
.prepare_cached("SELECT count() as count FROM plays")?
.query_row((), |row| row.get(0))?;
let your_play_count = query!(
let your_play_count: usize = conn
.prepare_cached(
"
SELECT count() as count
FROM plays
WHERE user_id=?
",
user.id
)
.fetch_one(&ctx.data().db)
.await?
.count;
)?
.query_row([user.id], |row| row.get(0))?;
let embed = CreateEmbed::default()
.title("Bot statistics")

View file

@ -14,7 +14,7 @@ macro_rules! edit_reply {
#[macro_export]
macro_rules! get_user {
($ctx:expr) => {{
crate::reply_errors!($ctx, crate::user::User::from_context($ctx).await)
crate::reply_errors!($ctx, crate::user::User::from_context($ctx))
}};
}

View file

@ -1,7 +1,6 @@
use r2d2_sqlite::SqliteConnectionManager;
use std::fs;
use sqlx::SqlitePool;
use crate::{
arcaea::{chart::SongCache, jacket::JacketCache},
assets::{get_data_dir, EXO_FONT, GEOSANS_FONT, KAZESAWA_BOLD_FONT, KAZESAWA_FONT},
@ -13,9 +12,11 @@ use crate::{
pub type Error = Box<dyn std::error::Error + Send + Sync>;
pub type Context<'a> = poise::Context<'a, UserContext, Error>;
pub type DbConnection = r2d2::Pool<SqliteConnectionManager>;
// Custom user data passed to all command functions
pub struct UserContext {
pub db: SqlitePool,
pub db: DbConnection,
pub song_cache: SongCache,
pub jacket_cache: JacketCache,
pub ui_measurements: UIMeasurements,
@ -29,11 +30,11 @@ pub struct UserContext {
impl UserContext {
#[inline]
pub async fn new(db: SqlitePool) -> Result<Self, Error> {
pub async fn new(db: DbConnection) -> Result<Self, Error> {
timed!("create_context", {
fs::create_dir_all(get_data_dir())?;
let mut song_cache = timed!("make_song_cache", { SongCache::new(&db).await? });
let mut song_cache = timed!("make_song_cache", { SongCache::new(&db)? });
let jacket_cache = timed!("make_jacket_cache", { JacketCache::new(&mut song_cache)? });
let ui_measurements = timed!("read_ui_measurements", { UIMeasurements::read()? });

View file

@ -22,9 +22,16 @@ mod user;
use arcaea::play::generate_missing_scores;
use assets::get_data_dir;
use context::{Error, UserContext};
use include_dir::{include_dir, Dir};
use poise::serenity_prelude::{self as serenity};
use sqlx::sqlite::SqlitePoolOptions;
use std::{env::var, sync::Arc, time::Duration};
use r2d2::Pool;
use r2d2_sqlite::SqliteConnectionManager;
use rusqlite_migration::Migrations;
use std::{
env::var,
sync::{Arc, LazyLock},
time::Duration,
};
// {{{ Error handler
async fn on_error(error: poise::FrameworkError<'_, UserContext, Error>) {
@ -40,13 +47,27 @@ async fn on_error(error: poise::FrameworkError<'_, UserContext, Error>) {
#[tokio::main]
async fn main() {
let pool = SqlitePoolOptions::new()
.connect(&format!(
"sqlite://{}/db.sqlite",
get_data_dir().to_str().unwrap()
))
.await
.unwrap();
let pool = Pool::new(
SqliteConnectionManager::file(&format!("{}/db.sqlite", get_data_dir().to_str().unwrap()))
.with_init(|conn| {
static MIGRATIONS_DIR: Dir = include_dir!("$CARGO_MANIFEST_DIR/migrations");
static MIGRATIONS: LazyLock<Migrations> = LazyLock::new(|| {
timed!("create_migration_structure", {
Migrations::from_directory(&MIGRATIONS_DIR)
.expect("Could not load migrations")
})
});
timed!("run_migrations", {
MIGRATIONS
.to_latest(conn)
.expect("Could not run migrations");
});
Ok(())
}),
)
.expect("Could not open sqlite database.");
// {{{ Poise options
let options = poise::FrameworkOptions {

View file

@ -1,9 +1,9 @@
use std::str::FromStr;
use poise::serenity_prelude::UserId;
use sqlx::SqlitePool;
use rusqlite::Row;
use crate::context::{Context, Error};
use crate::context::{Context, Error, UserContext};
#[derive(Debug, Clone)]
pub struct User {
@ -13,35 +13,44 @@ pub struct User {
}
impl User {
pub async fn from_context(ctx: &Context<'_>) -> Result<Self, Error> {
let id = ctx.author().id.get().to_string();
let user = sqlx::query!("SELECT * FROM users WHERE discord_id = ?", id)
.fetch_one(&ctx.data().db)
.await
.map_err(|_| "You are not an user in my database, sowwy ^~^")?;
Ok(User {
id: user.id as u32,
discord_id: user.discord_id,
is_pookie: user.is_pookie,
#[inline]
fn from_row<'a, 'b>(row: &'a Row<'b>) -> Result<Self, rusqlite::Error> {
Ok(Self {
id: row.get("id")?,
discord_id: row.get("discord_id")?,
is_pookie: row.get("is_pookie")?,
})
}
pub async fn by_id(db: &SqlitePool, id: u32) -> Result<Self, Error> {
let user = sqlx::query!("SELECT * FROM users WHERE id = ?", id)
.fetch_one(db)
.await?;
pub fn from_context(ctx: &Context<'_>) -> Result<Self, Error> {
let id = ctx.author().id.get().to_string();
let user = ctx
.data()
.db
.get()?
.prepare_cached("SELECT * FROM users WHERE discord_id = ?")?
.query_map([id], Self::from_row)?
.next()
.ok_or_else(|| "You are not an user in my database, sowwy ^~^")??;
Ok(User {
id: user.id as u32,
discord_id: user.discord_id,
is_pookie: user.is_pookie,
})
Ok(user)
}
pub fn by_id(ctx: &UserContext, id: u32) -> Result<Self, Error> {
let user = ctx
.db
.get()?
.prepare_cached("SELECT * FROM users WHERE id = ?")?
.query_map([id], Self::from_row)?
.next()
.ok_or_else(|| "You are not an user in my database, sowwy ^~^")??;
Ok(user)
}
}
#[inline]
pub async fn discord_it_to_discord_user(
pub async fn discord_id_to_discord_user(
&ctx: &Context<'_>,
discord_id: &str,
) -> Result<poise::serenity_prelude::User, Error> {