From 97f0a76c6c5950c6c425270715ece0eee666ff42 Mon Sep 17 00:00:00 2001 From: Alex Emmet <111742636+Alex-Emmet@users.noreply.github.com> Date: Wed, 7 Jan 2026 15:54:05 +0100 Subject: [PATCH] Format, Status, User Stati & Endpoints --- Cargo.lock | 260 +++++++++------- Cargo.toml | 1 + src/data/communication.rs | 24 ++ src/server/api.rs | 142 ++++++++- src/server/omikron_connection.rs | 511 +++++++++++++++++++++++++------ src/server/server.rs | 107 +------ src/server/socket.rs | 101 +++--- src/sql/connection_status.rs | 38 +++ src/sql/mod.rs | 1 + src/sql/sql.rs | 73 ++++- src/sql/user_online_tracker.rs | 99 +++++- 11 files changed, 962 insertions(+), 395 deletions(-) create mode 100644 src/sql/connection_status.rs diff --git a/Cargo.lock b/Cargo.lock index 45d3a1b..05b9215 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8,6 +8,7 @@ version = "0.1.0" dependencies = [ "aes-gcm", "ansi_term", + "async-trait", "async-tungstenite", "axum", "base64 0.22.1", @@ -27,7 +28,7 @@ dependencies = [ "rand 0.8.5", "rand_core 0.6.4", "reqwest", - "rustls 0.23.35", + "rustls 0.23.36", "rustls-pemfile 2.2.0", "sha1", "sha2", @@ -174,7 +175,7 @@ dependencies = [ "async-channel 2.5.0", "async-executor", "async-io 2.6.0", - "async-lock 3.4.1", + "async-lock 3.4.2", "blocking", "futures-lite 2.6.1", "once_cell", @@ -229,9 +230,9 @@ dependencies = [ [[package]] name = "async-lock" -version = "3.4.1" +version = "3.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fd03604047cee9b6ce9de9f70c6cd540a0520c813cbd49bae61f33ab80ed1dc" +checksum = "290f7f2596bd5b78a9fec8088ccd89180d7f9f55b94b0576823bbbdc72ee8311" dependencies = [ "event-listener 5.4.1", "event-listener-strategy", @@ -259,7 +260,7 @@ dependencies = [ "async-channel 1.9.0", "async-global-executor", "async-io 2.6.0", - "async-lock 3.4.1", + "async-lock 3.4.2", "crossbeam-utils", "futures-channel", "futures-core", @@ -296,10 +297,21 @@ dependencies = [ ] [[package]] -name = "async-tungstenite" -version = "0.32.0" +name = "async-trait" +version = "0.1.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6f89c129ab749940f95509d84950c62092c8b4bc6e386ddb162229037a6ec91" +checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "async-tungstenite" +version = "0.32.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8acc405d38be14342132609f06f02acaf825ddccfe76c4824a69281e0458ebd4" dependencies = [ "async-native-tls", "async-std", @@ -322,7 +334,7 @@ dependencies = [ "tokio-openssl", "tokio-rustls", "tungstenite", - "webpki-roots 1.0.4", + "webpki-roots 1.0.5", ] [[package]] @@ -348,9 +360,9 @@ checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "aws-lc-rs" -version = "1.15.1" +version = "1.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b5ce75405893cd713f9ab8e297d8e438f624dde7d706108285f7e17a25a180f" +checksum = "6a88aab2464f1f25453baa7a07c84c5b7684e274054ba06817f382357f77a288" dependencies = [ "aws-lc-sys", "zeroize", @@ -358,9 +370,9 @@ dependencies = [ [[package]] name = "aws-lc-sys" -version = "0.34.0" +version = "0.35.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "179c3777a8b5e70e90ea426114ffc565b2c1a9f82f6c4a0c5a34aa6ef5e781b6" +checksum = "b45afffdee1e7c9126814751f88dddc747f41d91da16c9551a0f1e8a11e788a1" dependencies = [ "cc", "cmake", @@ -370,9 +382,9 @@ dependencies = [ [[package]] name = "axum" -version = "0.8.7" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b098575ebe77cb6d14fc7f32749631a6e44edbef6b796f89b020e99ba20d425" +checksum = "8b52af3cb4058c895d37317bb27508dccc8e5f2d39454016b297bf4a400597b8" dependencies = [ "axum-core", "bytes", @@ -403,9 +415,9 @@ dependencies = [ [[package]] name = "axum-core" -version = "0.5.5" +version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59446ce19cd142f8833f856eb31f3eb097812d1479ab224f54d72428ca21ea22" +checksum = "08c78f31d7b1291f7ee735c1c6780ccde7785daae9a9206026862dab7d8792d1" dependencies = [ "bytes", "futures-core", @@ -449,9 +461,9 @@ checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] name = "base64ct" -version = "1.8.1" +version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e050f626429857a27ddccb31e0aca21356bfa709c04041aefddac081a8f068a" +checksum = "7d809780667f4410e7c41b07f52439b94d2bdf8528eeedc287fa38d3b7f95d82" [[package]] name = "bitflags" @@ -492,9 +504,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.19.0" +version = "3.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" +checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510" [[package]] name = "byteorder" @@ -519,9 +531,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.49" +version = "1.2.51" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90583009037521a116abf44494efecd645ba48b6622457080f080b85544e2215" +checksum = "7a0aeaff4ff1a90589618835a598e545176939b97874f7abc7851caa0618f203" dependencies = [ "find-msvc-tools", "jobserver", @@ -531,9 +543,9 @@ dependencies = [ [[package]] name = "cfg-expr" -version = "0.20.4" +version = "0.20.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9acd0bdbbf4b2612d09f52ba61da432140cb10930354079d0d53fafc12968726" +checksum = "21be0e1ce6cdb2ee7fff840f922fb04ead349e5cfb1e750b769132d44ce04720" dependencies = [ "smallvec", "target-lexicon", @@ -557,9 +569,9 @@ dependencies = [ [[package]] name = "cmake" -version = "0.1.56" +version = "0.1.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b042e5d8a74ae91bb0961acd039822472ec99f8ab0948cbf6d1369588f8be586" +checksum = "75443c44cd6b379beb8c5b45d85d0773baf31cce901fe7bb252f4eff3008ef7d" dependencies = [ "cc", ] @@ -920,9 +932,9 @@ checksum = "e825f6987101665dea6ec934c09ec6d721de7bc1bf92248e1d5810c8cd636b77" [[package]] name = "find-msvc-tools" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a3076410a55c90011c298b04d0cfa770b00fa04e1e3c97d3f6c9de105a03844" +checksum = "645cbb3a84e60b7531617d5ae4e57f7e27308f6445f5abf653209ea76dec8dff" [[package]] name = "flate2" @@ -1167,9 +1179,9 @@ checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7" [[package]] name = "gio" -version = "0.21.4" +version = "0.21.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "daeff3dd716d1ba91850b976b76a1c2d28f99ef6c1602cd8fdaa8fab8017fd9c" +checksum = "c5ff48bf600c68b476e61dc6b7c762f2f4eb91deef66583ba8bb815c30b5811a" dependencies = [ "futures-channel", "futures-core", @@ -1184,9 +1196,9 @@ dependencies = [ [[package]] name = "gio-sys" -version = "0.21.2" +version = "0.21.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "171ed2f6dd927abbe108cfd9eebff2052c335013f5879d55bab0dc1dee19b706" +checksum = "0071fe88dba8e40086c8ff9bbb62622999f49628344b1d1bf490a48a29d80f22" dependencies = [ "glib-sys", "gobject-sys", @@ -1197,9 +1209,9 @@ dependencies = [ [[package]] name = "glib" -version = "0.21.4" +version = "0.21.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b9dbecb1c33e483a98be4acfea2ab369e1c28f517c6eadb674537409c25c4b2" +checksum = "16de123c2e6c90ce3b573b7330de19be649080ec612033d397d72da265f1bd8b" dependencies = [ "bitflags 2.10.0", "futures-channel", @@ -1218,9 +1230,9 @@ dependencies = [ [[package]] name = "glib-macros" -version = "0.21.4" +version = "0.21.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "880e524e0085f3546cfb38532b2c202c0d64741d9977a6e4aa24704bfc9f19fb" +checksum = "cf59b675301228a696fe01c3073974643365080a76cc3ed5bc2cbc466ad87f17" dependencies = [ "heck", "proc-macro-crate", @@ -1231,9 +1243,9 @@ dependencies = [ [[package]] name = "glib-sys" -version = "0.21.2" +version = "0.21.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d09d3d0fddf7239521674e57b0465dfbd844632fec54f059f7f56112e3f927e1" +checksum = "2d95e1a3a19ae464a7286e14af9a90683c64d70c02532d88d87ce95056af3e6c" dependencies = [ "libc", "system-deps", @@ -1259,9 +1271,9 @@ dependencies = [ [[package]] name = "gobject-sys" -version = "0.21.2" +version = "0.21.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "538e41d8776173ec107e7b0f2aceced60abc368d7e1d81c1f0e2ecd35f59080d" +checksum = "2dca35da0d19a18f4575f3cb99fe1c9e029a2941af5662f326f738a21edaf294" dependencies = [ "glib-sys", "libc", @@ -1270,9 +1282,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.4.12" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3c0b69cfcb4e1b9f1bf2f53f95f766e4661169728ec61cd3fe5a0166f2d1386" +checksum = "2f44da3a8150a6703ed5d34e164b875fd14c2cdab9af1252a9a1020bde2bdc54" dependencies = [ "atomic-waker", "bytes", @@ -1447,7 +1459,7 @@ dependencies = [ "http", "hyper", "hyper-util", - "rustls 0.23.35", + "rustls 0.23.36", "rustls-pki-types", "tokio", "tokio-rustls", @@ -1660,9 +1672,9 @@ dependencies = [ [[package]] name = "iri-string" -version = "0.7.9" +version = "0.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f867b9d1d896b67beb18518eda36fdb77a32ea590de864f1325b294a6d14397" +checksum = "c91338f0783edbd6195decb37bae672fd3b165faffb89bf7b9e6942f8b1a731a" dependencies = [ "memchr", "serde", @@ -1670,9 +1682,9 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.15" +version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" +checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" [[package]] name = "jobserver" @@ -1726,9 +1738,9 @@ checksum = "2c4a545a15244c7d945065b5d392b2d2d7f21526fba56ce51467b06ed445e8f7" [[package]] name = "libc" -version = "0.2.178" +version = "0.2.179" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37c93d8daa9d8a012fd8ab92f088405fb202ea0b6ab73ee2482ae66af4f42091" +checksum = "c5a2d376baa530d1238d133232d15e239abad80d05838b4b59354e5268af431f" [[package]] name = "libm" @@ -1738,13 +1750,13 @@ checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" [[package]] name = "libredox" -version = "0.1.11" +version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df15f6eac291ed1cf25865b1ee60399f57e7c227e7f51bdbd4c5270396a9ed50" +checksum = "3d0b95e02c851351f877147b7deea7b1afb1df71b63aa5f8270716e0c5720616" dependencies = [ "bitflags 2.10.0", "libc", - "redox_syscall 0.6.0", + "redox_syscall 0.7.0", ] [[package]] @@ -1759,9 +1771,9 @@ dependencies = [ [[package]] name = "libz-rs-sys" -version = "0.5.4" +version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15413ef615ad868d4d65dce091cb233b229419c7c0c4bcaa746c0901c49ff39c" +checksum = "c10501e7805cee23da17c7790e59df2870c0d4043ec6d03f67d31e2b53e77415" dependencies = [ "zlib-rs", ] @@ -1870,7 +1882,7 @@ dependencies = [ "libc", "log", "openssl", - "openssl-probe", + "openssl-probe 0.1.6", "openssl-sys", "schannel", "security-framework 2.11.1", @@ -1886,9 +1898,9 @@ checksum = "43794a0ace135be66a25d3ae77d41b91615fb68ae937f904090203e81f755b65" [[package]] name = "ntapi" -version = "0.4.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8a3895c6391c39d7fe7ebc444a87eb2991b2a0bc718fdabd071eec617fc68e4" +checksum = "c70f219e21142367c70c0b30c6a9e3a14d55b4d12a204d897fbec83a0363f081" dependencies = [ "winapi", ] @@ -2017,6 +2029,12 @@ version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" +[[package]] +name = "openssl-probe" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f50d9b3dabb09ecd771ad0aa242ca6894994c130308ca3d7684634df8037391" + [[package]] name = "openssl-sys" version = "0.9.111" @@ -2313,18 +2331,18 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.103" +version = "1.0.105" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" +checksum = "535d180e0ecab6268a3e718bb9fd44db66bbbc256257165fc699dadf70d16fe7" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.42" +version = "1.0.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f" +checksum = "dc74d9a594b72ae6656596548f56f667211f8a97b3d4c3d467150794690dc40a" dependencies = [ "proc-macro2", ] @@ -2411,9 +2429,9 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.6.0" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec96166dafa0886eb81fe1c0a388bece180fbef2135f97c1e2cf8302e74b43b5" +checksum = "49f3fe0889e69e2ae9e41f4d6c4c0181701d00e4697b356fb1f74173a5e0ee27" dependencies = [ "bitflags 2.10.0", ] @@ -2503,9 +2521,9 @@ dependencies = [ [[package]] name = "rsa" -version = "0.9.9" +version = "0.9.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40a0376c50d0358279d9d643e4bf7b7be212f1f4ff1da9070a7b54d22ef75c88" +checksum = "b8573f03f5883dcaebdfcf4725caa1ecb9c15b2ef50c43a07b816e06799bb12d" dependencies = [ "const-oid", "digest", @@ -2568,9 +2586,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.35" +version = "0.23.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "533f54bc6a7d4f647e46ad909549eda97bf5afc1585190ef692b4286b198bd8f" +checksum = "c665f33d38cea657d9614f766881e4d510e0eda4239891eea56b4cadcf01801b" dependencies = [ "aws-lc-rs", "log", @@ -2583,11 +2601,11 @@ dependencies = [ [[package]] name = "rustls-native-certs" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9980d917ebb0c0536119ba501e90834767bffc3d60641457fd84a1f3fd337923" +checksum = "612460d5f7bea540c490b2b6395d8e34a953e52b491accd6c86c8164c5932a63" dependencies = [ - "openssl-probe", + "openssl-probe 0.2.0", "rustls-pki-types", "schannel", "security-framework 3.5.1", @@ -2613,9 +2631,9 @@ dependencies = [ [[package]] name = "rustls-pki-types" -version = "1.13.1" +version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "708c0f9d5f54ba0272468c1d306a52c495b31fa155e91bc25371e6df7996908c" +checksum = "21e6f2ab2928ca4291b86736a8bd920a277a399bba1589409d72154ff87c1282" dependencies = [ "zeroize", ] @@ -2650,9 +2668,9 @@ checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" [[package]] name = "ryu" -version = "1.0.20" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" +checksum = "a50f4cf475b65d88e057964e0e9bb1f0aa9bbb2036dc65c64596b42932536984" [[package]] name = "same-file" @@ -2756,15 +2774,15 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.145" +version = "1.0.149" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" +checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" dependencies = [ "itoa", "memchr", - "ryu", "serde", "serde_core", + "zmij", ] [[package]] @@ -2780,9 +2798,9 @@ dependencies = [ [[package]] name = "serde_spanned" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e24345aa0fe688594e73770a5f6d1b216508b4f93484c0026d521acd30134392" +checksum = "f8bbf91e5a4d6315eee45e704372590b30e260ee83af6639d64557f51b067776" dependencies = [ "serde_core", ] @@ -3129,9 +3147,9 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "syn" -version = "2.0.111" +version = "2.0.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "390cc9a294ab71bdb1aa2e99d13be9c753cd2d7bd6560c77118597410c4d2e87" +checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a" dependencies = [ "proc-macro2", "quote", @@ -3214,9 +3232,9 @@ checksum = "df7f62577c25e07834649fc3b39fafdc597c0a3527dc1c60129201ccfcbaa50c" [[package]] name = "tempfile" -version = "3.23.0" +version = "3.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d31c77bdf42a745371d260a26ca7163f1e0924b64afa0b688e61b5a9fa02f16" +checksum = "655da9c7eb6305c55742045d5a8d2037996d61d8de95806335c7c86ce0f82e9c" dependencies = [ "fastrand 2.3.0", "getrandom 0.3.4", @@ -3320,9 +3338,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.48.0" +version = "1.49.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff360e02eab121e0bc37a2d3b4d4dc622e6eda3a8e5253d5435ecf5bd4c68408" +checksum = "72a2903cd7736441aac9df9d7688bd0ce48edccaadf181c3b90be801e81d3d86" dependencies = [ "bytes", "libc", @@ -3373,7 +3391,7 @@ version = "0.26.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" dependencies = [ - "rustls 0.23.35", + "rustls 0.23.36", "tokio", ] @@ -3391,9 +3409,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.17" +version = "0.7.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2efa149fe76073d6e8fd97ef4f4eca7b67f599660115591483572e406e165594" +checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098" dependencies = [ "bytes", "futures-core", @@ -3408,9 +3426,9 @@ dependencies = [ [[package]] name = "toml" -version = "0.9.8" +version = "0.9.10+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0dc8b1fb61449e27716ec0e1bdf0f6b8f3e8f6b05391e8497b8b6d7804ea6d8" +checksum = "0825052159284a1a8b4d6c0c86cbc801f2da5afd2b225fa548c72f2e74002f48" dependencies = [ "indexmap", "serde_core", @@ -3423,18 +3441,18 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "0.7.3" +version = "0.7.5+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2cdb639ebbc97961c51720f858597f7f24c4fc295327923af55b74c3c724533" +checksum = "92e1cfed4a3038bc5a127e35a2d360f145e1f4b971b551a2ba5fd7aedf7e1347" dependencies = [ "serde_core", ] [[package]] name = "toml_edit" -version = "0.23.7" +version = "0.23.10+spec-1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6485ef6d0d9b5d0ec17244ff7eb05310113c3f316f2d14200d4de56b3cb98f8d" +checksum = "84c8b9f757e028cee9fa244aea147aab2a9ec09d5325a9b01e0a49730c2b5269" dependencies = [ "indexmap", "toml_datetime", @@ -3444,18 +3462,18 @@ dependencies = [ [[package]] name = "toml_parser" -version = "1.0.4" +version = "1.0.6+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0cbe268d35bdb4bb5a56a2de88d0ad0eb70af5384a99d648cd4b3d04039800e" +checksum = "a3198b4b0a8e11f09dd03e133c0280504d0801269e9afa46362ffde1cbeebf44" dependencies = [ "winnow", ] [[package]] name = "toml_writer" -version = "1.0.4" +version = "1.0.6+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df8b2b54733674ad286d16267dcfc7a71ed5c776e4ac7aa3c3e2561f7c637bf2" +checksum = "ab16f14aed21ee8bfd8ec22513f7287cd4a91aa92e44edfe2c17ddd004e92607" [[package]] name = "tower" @@ -3505,9 +3523,9 @@ checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" [[package]] name = "tracing" -version = "0.1.43" +version = "0.1.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d15d90a0b5c19378952d479dc858407149d7bb45a14de0142f6c534b16fc647" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" dependencies = [ "log", "pin-project-lite", @@ -3528,9 +3546,9 @@ dependencies = [ [[package]] name = "tracing-core" -version = "0.1.35" +version = "0.1.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a04e24fab5c89c6a36eb8558c9656f30d81de51dfa4d3b45f26b21d61fa0a6c" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" dependencies = [ "once_cell", "valuable", @@ -3576,7 +3594,7 @@ dependencies = [ "log", "native-tls", "rand 0.9.2", - "rustls 0.23.35", + "rustls 0.23.36", "rustls-pki-types", "sha1", "thiserror 2.0.17", @@ -3635,9 +3653,9 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "url" -version = "2.5.7" +version = "2.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08bc136a29a3d1758e07a9cca267be308aeebf5cfd5a10f3f67ab2097683ef5b" +checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" dependencies = [ "form_urlencoded", "idna", @@ -3833,9 +3851,9 @@ dependencies = [ [[package]] name = "webpki-roots" -version = "1.0.4" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2878ef029c47c6e8cf779119f20fcf52bde7ad42a731b2a304bc221df17571e" +checksum = "12bed680863276c63889429bfd6cab3b99943659923822de1c8a39c49e4d722c" dependencies = [ "rustls-pki-types", ] @@ -4251,9 +4269,9 @@ checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" [[package]] name = "winnow" -version = "0.7.13" +version = "0.7.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21a0236b59786fed61e2a80582dd500fe61f18b5dca67a4a067d0bc9039339cf" +checksum = "5a5364e9d77fcdeeaa6062ced926ee3381faa2ee02d3eb83a5c27a8825540829" dependencies = [ "memchr", ] @@ -4306,18 +4324,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.31" +version = "0.8.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd74ec98b9250adb3ca554bdde269adf631549f51d8a8f8f0a10b50f1cb298c3" +checksum = "1fabae64378cb18147bb18bca364e63bdbe72a0ffe4adf0addfec8aa166b2c56" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.31" +version = "0.8.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8a8d209fdf45cf5138cbb5a506f6b52522a25afccc534d1475dad8e31105c6a" +checksum = "c9c2d862265a8bb4471d87e033e730f536e2a285cc7cb05dbce09a2a97075f90" dependencies = [ "proc-macro2", "quote", @@ -4356,9 +4374,9 @@ dependencies = [ [[package]] name = "zeroize_derive" -version = "1.4.2" +version = "1.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" +checksum = "85a5b4158499876c763cb03bc4e49185d3cccbabb15b33c627f7884f43db852e" dependencies = [ "proc-macro2", "quote", @@ -4427,9 +4445,15 @@ dependencies = [ [[package]] name = "zlib-rs" -version = "0.5.4" +version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51f936044d677be1a1168fae1d03b583a285a5dd9d8cbf7b24c23aa1fc775235" +checksum = "40990edd51aae2c2b6907af74ffb635029d5788228222c4bb811e9351c0caad3" + +[[package]] +name = "zmij" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fc5a66a20078bf1251bde995aa2fdcc4b800c70b5d92dd2c62abc5c60f679f8" [[package]] name = "zopfli" diff --git a/Cargo.toml b/Cargo.toml index 1331a56..d5e2dcb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,6 +6,7 @@ edition = "2024" [dependencies] aes-gcm = "*" ansi_term = "0.12.1" +async-trait = "0.1.89" async-tungstenite = { version = "0.32.0", features = ["futures-03-sink", "futures-util", "handshake", "__rustls-tls", "async-native-tls", "async-std", "async-std-runtime", "async-tls", "gio", "gio-runtime", "glib", "openssl", "real-async-native-tls", "real-async-tls", "real-native-tls", "real-tokio-native-tls", "real-tokio-openssl", "real-tokio-rustls", "rustls-native-certs", "rustls-pki-types", "tokio", "tokio-native-tls", "tokio-openssl", "tokio-runtime", "tokio-rustls-manual-roots", "tokio-rustls-native-certs", "tokio-rustls-webpki-roots", "url", "verbose-logging", "webpki-roots" ] } axum = "0.8.7" base64 = "0.22.1" diff --git a/src/data/communication.rs b/src/data/communication.rs index 58fd957..e21dcae 100644 --- a/src/data/communication.rs +++ b/src/data/communication.rs @@ -78,6 +78,9 @@ pub enum DataTypes { communities, rho_connections, user, + online_status, + omikron_id, + omikron_connections, } impl DataTypes { @@ -155,6 +158,9 @@ impl DataTypes { "communities" => DataTypes::communities, "rhoconnections" => DataTypes::rho_connections, "user" => DataTypes::user, + "onlinestatus" => DataTypes::online_status, + "omikronid" => DataTypes::omikron_id, + "omikronconnections" => DataTypes::omikron_connections, _ => DataTypes::error_type, // fallback if unknown } } @@ -231,6 +237,15 @@ pub enum CommunicationType { iota_connected, iota_disconnected, sync_client_iota_status, + + get_user_data, + get_iota_data, + + change_user_data, + change_iota_data, + + start_register, + complete_register, } impl CommunicationType { pub fn parse(p0: String) -> CommunicationType { @@ -305,6 +320,15 @@ impl CommunicationType { "userdisconnected" => CommunicationType::user_disconnected, "syncclientiotastatus" => CommunicationType::sync_client_iota_status, + "getuserdata" => CommunicationType::get_user_data, + "getiotadata" => CommunicationType::get_iota_data, + + "changeuserdata" => CommunicationType::change_user_data, + "changeiotadata" => CommunicationType::change_iota_data, + + "startregister" => CommunicationType::start_register, + "completeregister" => CommunicationType::complete_register, + _ => CommunicationType::error, } } diff --git a/src/server/api.rs b/src/server/api.rs index 3b5fbcc..69077a4 100644 --- a/src/server/api.rs +++ b/src/server/api.rs @@ -1,13 +1,10 @@ -use crate::{get_public_key, log}; +use crate::data::communication::{CommunicationType, CommunicationValue, DataTypes}; +use crate::get_public_key; +use crate::sql::sql; use crate::{ sql::{ - iota_omikron_tracker::{get_omikron_for_iota, track_iota_omikron, untrack_iota}, - sql::{ - change_about, change_avatar, change_display_name, change_iota_id, change_iota_key, - change_keys, change_status, change_username, get_by_id, get_by_username, - get_iota_by_id, get_omikron_by_id, get_random_omikron, get_register_id, - register_complete_iota, register_complete_user, - }, + iota_omikron_tracker::get_omikron_for_iota, + sql::{get_by_user_id, get_omikron_by_id, get_random_omikron}, }, util::crypto_helper::public_key_to_base64, }; @@ -16,6 +13,7 @@ use http_body_util::Full; use hyper::body::Bytes; use hyper::{HeaderMap, Response as HttpResponse, StatusCode}; use json::JsonValue; +use json::number::Number; pub async fn handle( path: &str, @@ -37,11 +35,6 @@ pub async fn handle( // get/ // omikron/ // id/ - // register/ - // innit/ - // complete - log!("{}", path); - log!("{:?} .len = {}", path_parts, path_parts.len()); let (status, content, body_text) = if path_parts.len() >= 2 { match path_parts[1] { "get" => match path_parts[2] { @@ -90,7 +83,7 @@ pub async fn handle( not_found() } } else if let Ok((_, iota_id, _, _, _, _, _, _, _, _, _, _)) = - get_by_id(id).await + get_by_user_id(id).await { if let Some(omikron_id) = get_omikron_for_iota(iota_id).await { if let Ok((public_key, ip_address)) = @@ -119,14 +112,131 @@ pub async fn handle( } // get/id/ "id" => { - let username = path_parts[3]; - bad_request() + let username = path_parts[2]; + if username.is_empty() { + not_found() + } else { + if let Ok(( + id, + iota_id, + username, + display, + status, + about, + avatar, + sub_level, + sub_end, + public_key, + _, + _, + )) = sql::get_by_username(username).await + { + ( + StatusCode::OK, + "application/json", + CommunicationValue::new(CommunicationType::success) + .add_data_str(DataTypes::username, username) + .add_data_str(DataTypes::public_key, public_key) + .add_data( + DataTypes::user_id, + JsonValue::Number(Number::from(id)), + ) + .add_data( + DataTypes::iota_id, + JsonValue::Number(Number::from(iota_id)), + ) + .add_data_str(DataTypes::display, display) + .add_data_str(DataTypes::status, status) + .add_data_str(DataTypes::about, about) + .add_data_str(DataTypes::avatar, avatar) + .add_data( + DataTypes::sub_level, + JsonValue::Number(Number::from(sub_level)), + ) + .add_data( + DataTypes::sub_end, + JsonValue::Number(Number::from(sub_end)), + ) + .to_json() + .to_string(), + ) + } else { + ( + StatusCode::OK, + "application/json", + CommunicationValue::new(CommunicationType::error_not_found) + .to_json() + .to_string(), + ) + } + } } "public_key" => ( StatusCode::OK, "application/json", public_key_to_base64(&get_public_key()), ), + "user" => { + let id = path_parts[2]; + let id: i64 = id.parse().unwrap_or(0); + if id == 0 { + bad_request() + } else { + if let Ok(( + id, + iota_id, + username, + display, + status, + about, + avatar, + sub_level, + sub_end, + public_key, + _, + _, + )) = sql::get_by_user_id(id).await + { + ( + StatusCode::OK, + "application/json", + CommunicationValue::new(CommunicationType::success) + .add_data_str(DataTypes::username, username) + .add_data_str(DataTypes::public_key, public_key) + .add_data( + DataTypes::user_id, + JsonValue::Number(Number::from(id)), + ) + .add_data( + DataTypes::iota_id, + JsonValue::Number(Number::from(iota_id)), + ) + .add_data_str(DataTypes::display, display) + .add_data_str(DataTypes::status, status) + .add_data_str(DataTypes::about, about) + .add_data_str(DataTypes::avatar, avatar) + .add_data( + DataTypes::sub_level, + JsonValue::Number(Number::from(sub_level)), + ) + .add_data( + DataTypes::sub_end, + JsonValue::Number(Number::from(sub_end)), + ) + .to_json() + .to_string(), + ) + } else { + ( + StatusCode::OK, + "application/json", + CommunicationValue::new(CommunicationType::error_not_found) + .to_json() + .to_string(), + ) + } + } + } _ => { let id = path_parts[2]; let id: i64 = id.parse().unwrap_or(0); diff --git a/src/server/omikron_connection.rs b/src/server/omikron_connection.rs index d9ba663..f1bd08f 100644 --- a/src/server/omikron_connection.rs +++ b/src/server/omikron_connection.rs @@ -1,11 +1,7 @@ use crate::data::communication::{CommunicationType, CommunicationValue, DataTypes}; -use crate::sql::iota_omikron_tracker::{ - track_iota_omikron, untrack_by_omikron as untrack_iota_by_omikron, untrack_iota, -}; -use crate::sql::sql::get_omikron_by_id; -use crate::sql::user_online_tracker::{ - track_user_omikron, untrack_by_omikron as untrack_user_by_omikron, untrack_user, -}; +use crate::sql::connection_status::ConnectionType; +use crate::sql::sql::{self, get_by_user_id, get_by_username, get_iota_by_id, get_omikron_by_id}; +use crate::sql::user_online_tracker::{self}; use crate::util::crypto_helper::encrypt; use crate::util::logger::PrintType; use crate::{get_private_key, log_out}; @@ -18,6 +14,7 @@ use futures::stream::SplitStream; use hyper::upgrade::Upgraded; use hyper_util::rt::TokioIo; use json::JsonValue; +use json::number::Number; use rand::Rng; use rand::distributions::Alphanumeric; use std::sync::Arc; @@ -73,7 +70,7 @@ impl OmikronConnection { } sender.send(message_text).await.unwrap(); } - pub async fn get_user_id(&self) -> i64 { + pub async fn get_omikron_id(&self) -> i64 { *self.omikron_id.read().await } pub async fn is_identified(&self) -> bool { @@ -102,137 +99,168 @@ impl OmikronConnection { return; } - // Handle identification - if !*self.identified.read().await && cv.is_type(CommunicationType::identification) { - let omikron_id = cv - .get_data(DataTypes::omikron) - .unwrap_or(&JsonValue::Null) - .as_i64() - .unwrap_or(0); - match get_omikron_by_id(omikron_id).await { - Ok((public_key, _)) => { - // Generate Challenge, encrypt it and send it to the omikron - *self.omikron_id.write().await = omikron_id; + // If not yet identified + if !self.is_identified().await { + // handle identification + if !*self.identified.read().await && cv.is_type(CommunicationType::identification) { + let omikron_id = cv + .get_data(DataTypes::omikron) + .unwrap_or(&JsonValue::Null) + .as_i64() + .unwrap_or(0); + match get_omikron_by_id(omikron_id).await { + Ok((public_key, _)) => { + // Generate Challenge, encrypt it and send it to the omikron + *self.omikron_id.write().await = omikron_id; - let challenge_str: String = rand::thread_rng() - .sample_iter(&Alphanumeric) - .take(32) - .map(char::from) - .collect(); + let challenge_str: String = rand::thread_rng() + .sample_iter(&Alphanumeric) + .take(32) + .map(char::from) + .collect(); - *self.challenge.write().await = challenge_str.clone(); + *self.challenge.write().await = challenge_str.clone(); - let user_public_key_bytes = match STANDARD.decode(&public_key) { - Ok(bytes) => bytes, - Err(_) => { - self.send_error_response( - &cv.get_id(), - CommunicationType::error_invalid_omikron_id, - ) - .await; - return; - } - }; - *self.pub_key.write().await = Some(user_public_key_bytes.clone()); - - let omikron_pub_key: PublicKey = - match PublicKey::from_bytes(&user_public_key_bytes) { - Some(key) => key, - None => { + let user_public_key_bytes = match STANDARD.decode(&public_key) { + Ok(bytes) => bytes, + Err(_) => { self.send_error_response( &cv.get_id(), - CommunicationType::error_invalid_public_key, + CommunicationType::error_invalid_omikron_id, ) .await; return; } }; + *self.pub_key.write().await = Some(user_public_key_bytes.clone()); - let encrypted_challenge = - encrypt(get_private_key(), omikron_pub_key, &challenge_str) - .unwrap_or("".to_string()); + let omikron_pub_key: PublicKey = + match PublicKey::from_bytes(&user_public_key_bytes) { + Some(key) => key, + _ => { + self.send_error_response( + &cv.get_id(), + CommunicationType::error_invalid_public_key, + ) + .await; + return; + } + }; - let response = CommunicationValue::new(CommunicationType::challenge) - .add_data_str( - DataTypes::public_key, - STANDARD.encode(get_public_key().as_bytes()), + let encrypted_challenge = + encrypt(get_private_key(), omikron_pub_key, &challenge_str) + .unwrap_or("".to_string()); + + let response = CommunicationValue::new(CommunicationType::challenge) + .add_data_str( + DataTypes::public_key, + STANDARD.encode(get_public_key().as_bytes()), + ) + .add_data_str(DataTypes::challenge, encrypted_challenge) + .with_id(cv.get_id()); + + self.send_message(&response).await; + *self.identified.write().await = true; + return; + } + Err(e) => { + self.send_message( + &CommunicationValue::new(CommunicationType::error_not_authenticated) + .with_id(cv.get_id()) + .add_data_str(DataTypes::error_type, e.to_string()), ) - .add_data_str(DataTypes::challenge, encrypted_challenge) - .with_id(cv.get_id()); + .await; - self.send_message(&response).await; - *self.identified.write().await = true; - return; + return; + } } - Err(e) => { - self.send_message( - &CommunicationValue::new(CommunicationType::error_not_authenticated) - .with_id(cv.get_id()) - .add_data_str(DataTypes::error_type, e.to_string()), + } + + // Handle challenge response + if *self.identified.read().await + && !*self.challenged.read().await + && cv.is_type(CommunicationType::challenge_response) + { + let client_response = cv + .get_data(DataTypes::challenge) + .unwrap_or(&JsonValue::Null) + .as_str() + .unwrap_or(""); + let expected_challenge = self.challenge.read().await.clone(); + + if client_response == expected_challenge { + *self.challenged.write().await = true; + + let response = + CommunicationValue::new(CommunicationType::identification_response) + .with_id(cv.get_id()); + let _ = sql::set_omikron_active(self.get_omikron_id().await, true); + self.send_message(&response).await; + } else { + self.send_error_response( + &cv.get_id(), + CommunicationType::error_invalid_challenge, ) .await; - - return; + self.close().await; } + return; } - } - // Handle challenge response - if *self.identified.read().await - && !*self.challenged.read().await - && cv.is_type(CommunicationType::challenge_response) - { - let client_response = cv - .get_data(DataTypes::challenge) - .unwrap_or(&JsonValue::Null) - .as_str() - .unwrap_or(""); - let expected_challenge = self.challenge.read().await.clone(); - - if client_response == expected_challenge { - *self.challenged.write().await = true; - - let response = CommunicationValue::new(CommunicationType::identification_response) - .with_id(cv.get_id()); - - self.send_message(&response).await; - } else { - self.send_error_response(&cv.get_id(), CommunicationType::error_invalid_challenge) - .await; - self.close().await; - } - return; - } - - if !self.is_identified().await { + // if not identified && not identifying self.send_error_response(&cv.get_id(), CommunicationType::error_not_authenticated) .await; self.close().await; return; } - let omikron_id = self.get_user_id().await; + let omikron_id = self.get_omikron_id().await; if cv.is_type(CommunicationType::user_connected) { if let Some(user_id) = cv.get_data(DataTypes::user_id).and_then(|v| v.as_i64()) { - track_user_omikron(user_id, omikron_id).await; + user_online_tracker::track_user_status(user_id, ConnectionType::Online, omikron_id) + .await; } return; } if cv.is_type(CommunicationType::user_disconnected) { if let Some(user_id) = cv.get_data(DataTypes::user_id).and_then(|v| v.as_i64()) { - untrack_user(user_id).await; + if let Some(status) = user_online_tracker::get_user_status(user_id).await { + user_online_tracker::track_user_status( + user_id, + ConnectionType::UserOffline, + status.omikron_id, + ) + .await; + } } return; } if cv.is_type(CommunicationType::iota_connected) { if let Some(iota_id) = cv.get_data(DataTypes::iota_id).and_then(|v| v.as_i64()) { - track_iota_omikron(iota_id, omikron_id).await; + user_online_tracker::track_iota_connection(iota_id, omikron_id).await; + if let Ok(users) = sql::get_users_by_iota_id(iota_id).await { + for user in users { + user_online_tracker::track_user_status( + user.0, + ConnectionType::UserOffline, + omikron_id, + ) + .await; + } + } } return; } if cv.is_type(CommunicationType::iota_disconnected) { if let Some(iota_id) = cv.get_data(DataTypes::iota_id).and_then(|v| v.as_i64()) { - untrack_iota(iota_id).await; + let iota_offline = + user_online_tracker::untrack_iota_connection(iota_id, omikron_id).await; + if iota_offline { + if let Ok(users) = sql::get_users_by_iota_id(iota_id).await { + let user_ids: Vec = users.iter().map(|u| u.0).collect(); + user_online_tracker::untrack_many_users(&user_ids).await; + } + } } return; } @@ -242,7 +270,12 @@ impl OmikronConnection { { for user_id_json in user_ids { if let Some(user_id) = user_id_json.as_i64() { - track_user_omikron(user_id, omikron_id).await; + user_online_tracker::track_user_status( + user_id, + ConnectionType::Online, + omikron_id, + ) + .await; } } } @@ -251,12 +284,286 @@ impl OmikronConnection { { for iota_id_json in iota_ids { if let Some(iota_id) = iota_id_json.as_i64() { - track_iota_omikron(iota_id, omikron_id).await; + user_online_tracker::track_iota_connection(iota_id, omikron_id).await; } } } return; } + if cv.is_type(CommunicationType::get_user_data) { + if let Some(user_id) = cv.get_data(DataTypes::user_id).cloned() { + if let Some(user_id) = user_id.as_i64() { + if let Ok(( + id, + iota_id, + username, + display, + status, + about, + avatar, + sub_level, + sub_end, + public_key, + _, + _, + )) = get_by_user_id(user_id).await + { + let mut response = + CommunicationValue::new(CommunicationType::get_user_data) + .add_data_str(DataTypes::username, username) + .add_data_str(DataTypes::public_key, public_key) + .add_data(DataTypes::user_id, JsonValue::Number(Number::from(id))) + .add_data( + DataTypes::iota_id, + JsonValue::Number(Number::from(iota_id)), + ) + .add_data_str(DataTypes::display, display) + .add_data_str(DataTypes::status, status) + .add_data_str(DataTypes::about, about) + .add_data_str(DataTypes::avatar, avatar) + .add_data( + DataTypes::sub_level, + JsonValue::Number(Number::from(sub_level)), + ) + .add_data( + DataTypes::sub_end, + JsonValue::Number(Number::from(sub_end)), + ); + + let user_status = user_online_tracker::get_user_status(id).await; + let iota_connections = + user_online_tracker::get_iota_omikron_connections(iota_id) + .await + .unwrap_or_default(); + response = response.add_data( + DataTypes::omikron_connections, + JsonValue::Array( + iota_connections + .into_iter() + .map(|id| JsonValue::Number(Number::from(id))) + .collect(), + ), + ); + + if let Some(user_status) = user_status { + response = response.add_data( + DataTypes::online_status, + JsonValue::String(user_status.connection_type.to_string()), + ); + response = response.add_data( + DataTypes::omikron_id, + JsonValue::Number(Number::from(user_status.omikron_id)), + ); + } else { + response = response.add_data( + DataTypes::online_status, + JsonValue::String(ConnectionType::IotaOffline.to_string()), + ); + } + + self.send_message(&response).await; + return; + } + } + } + if let Some(username) = cv.get_data(DataTypes::username).cloned() { + if let Some(username) = username.as_str() { + if let Ok(( + id, + iota_id, + username, + display, + status, + about, + avatar, + sub_level, + sub_end, + public_key, + _, + _, + )) = get_by_username(username).await + { + let mut response = + CommunicationValue::new(CommunicationType::get_user_data) + .add_data_str(DataTypes::username, username) + .add_data_str(DataTypes::public_key, public_key) + .add_data(DataTypes::user_id, JsonValue::Number(Number::from(id))) + .add_data( + DataTypes::iota_id, + JsonValue::Number(Number::from(iota_id)), + ) + .add_data_str(DataTypes::display, display) + .add_data_str(DataTypes::status, status) + .add_data_str(DataTypes::about, about) + .add_data_str(DataTypes::avatar, avatar) + .add_data( + DataTypes::sub_level, + JsonValue::Number(Number::from(sub_level)), + ) + .add_data( + DataTypes::sub_end, + JsonValue::Number(Number::from(sub_end)), + ); + + let user_status = user_online_tracker::get_user_status(id).await; + let iota_connections = + user_online_tracker::get_iota_omikron_connections(iota_id) + .await + .unwrap_or_default(); + + if let Some(user_status) = user_status { + response = response.add_data( + DataTypes::online_status, + JsonValue::String(user_status.connection_type.to_string()), + ); + response = response.add_data( + DataTypes::omikron_id, + JsonValue::Number(Number::from(user_status.omikron_id)), + ); + } else { + response = response.add_data( + DataTypes::online_status, + JsonValue::String(ConnectionType::IotaOffline.to_string()), + ); + } + response = response.add_data( + DataTypes::omikron_connections, + JsonValue::Array( + iota_connections + .iter() + .map(|&id| JsonValue::Number(Number::from(id))) + .collect(), + ), + ); + + self.send_message(&response).await; + return; + } + } + } + let response = + CommunicationValue::new(CommunicationType::error_not_found).with_id(cv.get_id()); + self.send_message(&response).await; + + return; + } + if cv.is_type(CommunicationType::get_iota_data) { + if let Some(iota_id) = cv.get_data(DataTypes::iota_id).cloned() { + if let Some(iota_id) = iota_id.as_i64() { + if let Ok((iota_id, public_key)) = get_iota_by_id(iota_id).await { + let mut response = + CommunicationValue::new(CommunicationType::get_iota_data) + .add_data_str(DataTypes::public_key, public_key) + .add_data( + DataTypes::iota_id, + JsonValue::Number(Number::from(iota_id)), + ); + + let iota_connections = + user_online_tracker::get_iota_omikron_connections(iota_id) + .await + .unwrap_or_default(); + + response = response.add_data( + DataTypes::omikron_connections, + JsonValue::Array( + iota_connections + .iter() + .map(|&id| JsonValue::Number(Number::from(id))) + .collect(), + ), + ); + self.send_message(&response).await; + return; + } + } + } + if let Some(user_id) = cv.get_data(DataTypes::user_id).cloned() { + if let Some(user_id) = user_id.as_i64() { + if let Ok((_, iota_id, _, _, _, _, _, _, _, _, _, _)) = + get_by_user_id(user_id).await + { + if let Ok((iota_id, public_key)) = get_iota_by_id(iota_id).await { + let mut response = + CommunicationValue::new(CommunicationType::get_iota_data) + .add_data_str(DataTypes::public_key, public_key) + .add_data( + DataTypes::user_id, + JsonValue::Number(Number::from(user_id)), + ) + .add_data( + DataTypes::iota_id, + JsonValue::Number(Number::from(iota_id)), + ); + + let iota_connections = + user_online_tracker::get_iota_omikron_connections(iota_id) + .await + .unwrap_or_default(); + + response = response.add_data( + DataTypes::omikron_connections, + JsonValue::Array( + iota_connections + .iter() + .map(|&id| JsonValue::Number(Number::from(id))) + .collect(), + ), + ); + + self.send_message(&response).await; + return; + } + } + } + } + if let Some(username) = cv.get_data(DataTypes::username).cloned() { + if let Some(username) = username.as_str() { + if let Ok((user_id, iota_id, _, _, _, _, _, _, _, _, _, _)) = + get_by_username(username).await + { + if let Ok((iota_id, public_key)) = get_iota_by_id(iota_id).await { + let mut response = + CommunicationValue::new(CommunicationType::get_iota_data) + .add_data_str(DataTypes::public_key, public_key) + .add_data( + DataTypes::user_id, + JsonValue::Number(Number::from(user_id)), + ) + .add_data_str(DataTypes::username, username.to_string()) + .add_data( + DataTypes::iota_id, + JsonValue::Number(Number::from(iota_id)), + ); + + let iota_connections = + user_online_tracker::get_iota_omikron_connections(iota_id) + .await + .unwrap_or_default(); + + response = response.add_data( + DataTypes::omikron_connections, + JsonValue::Array( + iota_connections + .iter() + .map(|&id| JsonValue::Number(Number::from(id))) + .collect(), + ), + ); + self.send_message(&response).await; + return; + } + } + } + } + let response = + CommunicationValue::new(CommunicationType::error_not_found).with_id(cv.get_id()); + self.send_message(&response).await; + + return; + } + if cv.is_type(CommunicationType::change_user_data) {} + if cv.is_type(CommunicationType::change_iota_data) {} } async fn send_error_response(&self, message_id: &Uuid, error_type: CommunicationType) { @@ -265,14 +572,16 @@ impl OmikronConnection { } pub async fn close(&self) { let mut sender = self.sender.write().await; + if self.is_identified().await { + let _ = sql::set_omikron_active(self.get_omikron_id().await, false); + } let _ = sender.close().await; } pub async fn handle_close(self: Arc) { if self.is_identified().await { - let omikron_id = self.get_user_id().await; + let omikron_id = self.get_omikron_id().await; if omikron_id != 0 { - untrack_iota_by_omikron(omikron_id).await; - untrack_user_by_omikron(omikron_id).await; + user_online_tracker::untrack_omikron(omikron_id).await; } } } diff --git a/src/server/server.rs b/src/server/server.rs index 263ff88..da1763d 100644 --- a/src/server/server.rs +++ b/src/server/server.rs @@ -1,12 +1,11 @@ use crate::log; use crate::server::api; -use crate::server::omikron_connection::OmikronConnection; +use crate::server::socket; use crate::util::file_util::load_file_buf; use base64::Engine; use base64::engine::general_purpose::STANDARD; use bytes::Bytes; -use futures::StreamExt; use futures_util::TryFutureExt; use http_body_util::BodyExt; use http_body_util::Full; @@ -30,9 +29,8 @@ use std::{future::Future, pin::Pin, time::Duration}; use tokio::net::TcpListener; use tokio::sync::broadcast; use tokio_rustls::TlsAcceptor; -use tokio_tungstenite::{WebSocketStream, accept_async}; use tower::Service; -use tungstenite::Message; + #[derive(Clone)] struct HttpService { peer_addr: SocketAddr, @@ -60,7 +58,7 @@ impl Service> for HttpService { let headers = parts.headers.clone(); let fut = async move { - let is_websocket_upgrade = path == "/ws/omikron" + let is_websocket_upgrade = path == "/ws" && method == Method::GET && headers .get("connection") @@ -94,39 +92,7 @@ impl Service> for HttpService { let upgrades = upgrade::on(req_for_upgrade); log!("Handling WebSocket upgrade"); - // Spawn upgrade handling to avoid blocking the service call - tokio::spawn(async move { - match upgrades.await { - Ok(upgraded_stream) => { - log!("Valid WebSocket upgrade"); - let raw_stream = TokioIo::new(upgraded_stream); - - let ws_stream = WebSocketStream::from_raw_socket( - raw_stream, - tungstenite::protocol::Role::Server, - None, - ) - .await; - log!( - "WebSocket handshake successful, handling connection for Omikron" - ); - - // Split stream for OmikronConnection - let (writer, reader) = ws_stream.split(); - - // INTEGRATION START - // Erstelle die Connection und starte den Handler - let connection = OmikronConnection::new(writer, reader); - - // Handler in separatem Task starten - start_omikron_handler(connection).await; - // INTEGRATION END - } - Err(e) => { - log!("WebSocket upgrade failed after response: {:?}", e); - } - } - }); + socket::handle(path, upgrades); log!("Handled WebSocket connection initiation"); Ok(response) @@ -192,7 +158,6 @@ async fn run_http_server(port: u16) -> bool { port ); - // Create a broadcast channel for graceful shutdown signal let (shutdown_tx, _) = broadcast::channel::<()>(1); tokio::spawn(async move { @@ -205,7 +170,6 @@ async fn run_http_server(port: u16) -> bool { let service = HttpService { peer_addr: addr }; let io = TokioIo::new(stream); - // Subscribe to the shutdown signal for this specific connection let mut rx = shutdown_tx.subscribe(); tokio::spawn(async move { @@ -216,7 +180,6 @@ async fn run_http_server(port: u16) -> bool { .serve_connection(io, TowerToHyperService::new(service)) .with_upgrades(); - // Wait for either the connection to finish naturally OR the shutdown signal tokio::select! { res = conn => { if let Err(err) = res { @@ -276,13 +239,11 @@ async fn run_tls_server(port: u16, tls_config: Arc) -> bool { port ); - // Create a broadcast channel for graceful shutdown signal let (shutdown_tx, _) = broadcast::channel::<()>(1); tokio::spawn(async move { loop { tokio::select! { - // Monitor for shutdown signal _ = async { loop { tokio::time::sleep(Duration::from_millis(100)).await; @@ -294,7 +255,6 @@ async fn run_tls_server(port: u16, tls_config: Arc) -> bool { break; } - // Accept new connections accepted = listener.accept() => { match accepted { std::result::Result::Ok((stream, addr)) => { @@ -317,14 +277,12 @@ async fn run_tls_server(port: u16, tls_config: Arc) -> bool { }; let io = TokioIo::new(tls_stream); - // Prepare connection future let conn = http1::Builder::new() .preserve_header_case(true) .title_case_headers(true) .serve_connection(io, TowerToHyperService::new(service)) .with_upgrades(); - // Wait for either the connection to finish naturally OR the shutdown signal tokio::select! { res = conn => { if let Err(err) = res { @@ -363,7 +321,7 @@ pub async fn start(port: u16) -> bool { match tls_result { Ok(Some(tls_config)) => run_tls_server(port, tls_config).await, - Ok(None) => run_http_server(port).await, + Ok(_) => run_http_server(port).await, Err(e) => { log!("Fatal error during TLS config load: {}", e); false @@ -376,7 +334,7 @@ fn calculate_accept_key(key: &str) -> String { sha1.update(key.as_bytes()); sha1.update(websocket_guid.as_bytes()); let result = sha1.finalize(); - STANDARD.encode(result) // Base64 encode the result + STANDARD.encode(result) } /// Loads TLS config. Returns Ok(None) if cert files are not found, and an error if parsing fails. @@ -391,7 +349,7 @@ fn load_tls_config() -> Result>, Box> { log!("TLS certificate 'certs/cert.pem' not found."); return Ok(None); } - Err(e) => return Err(e.into()), // Other IO error + Err(e) => return Err(e.into()), }; let key_file_buf = match key_file_res { @@ -400,7 +358,7 @@ fn load_tls_config() -> Result>, Box> { log!("TLS key 'certs/cert.key' not found."); return Ok(None); } - Err(e) => return Err(e.into()), // Other IO error + Err(e) => return Err(e.into()), }; // Continue with configuration if both files were found @@ -411,12 +369,12 @@ fn load_tls_config() -> Result>, Box> { // PKCS8 let mut key_reader = BufReader::new(key_file_buf); let mut key_ders = rustls_pemfile::pkcs8_private_keys(&mut key_reader) - .map(|r| r.map(Into::into)) // Explicit conversion + .map(|r| r.map(Into::into)) .collect::, io::Error>>()?; if key_ders.is_empty() { // RSA - key_reader = BufReader::new(load_file_buf("certs", "cert.key")?); // Re-read key file + key_reader = BufReader::new(load_file_buf("certs", "cert.key")?); key_ders = rustls_pemfile::rsa_private_keys(&mut key_reader) .map(|r| r.map(Into::into)) .collect::, io::Error>>()?; @@ -424,14 +382,14 @@ fn load_tls_config() -> Result>, Box> { if key_ders.is_empty() { // EC - key_reader = BufReader::new(load_file_buf("certs", "cert.key")?); // Re-read key file + key_reader = BufReader::new(load_file_buf("certs", "cert.key")?); key_ders = rustls_pemfile::ec_private_keys(&mut key_reader) .map(|r| r.map(Into::into)) .collect::, io::Error>>()?; } if key_ders.is_empty() { - return Err("No private keys found in key file. (Tried PKCS8, RSA, and EC)".into()); + return Err("No valid private keys found in key file (Tried PKCS8, RSA and EC).".into()); } let config = rustls::ServerConfig::builder() @@ -441,44 +399,3 @@ fn load_tls_config() -> Result>, Box> { Ok(Some(Arc::new(config))) } -// In deiner Server-Logik, wo OmikronConnection initialisiert wird: - -pub async fn start_omikron_handler(connection: Arc) { - loop { - let msg = match { - let mut receiver = connection.receiver.write().await; - receiver.next().await - } { - Some(Ok(msg)) => msg, - Some(Err(e)) => { - log!("WS Error: {}", e); - break; - } - None => break, // Stream ended - }; - - match msg { - Message::Text(text) => { - let conn_clone = connection.clone(); - tokio::spawn(async move { - conn_clone.handle_message(text.to_string()).await; - }); - } - Message::Ping(_) => { - let pong_response = crate::data::communication::CommunicationValue::new( - crate::data::communication::CommunicationType::pong, - ); - let conn_clone = connection.clone(); - tokio::spawn(async move { - conn_clone.send_message(&pong_response).await; - }); - } - Message::Close(_) => { - break; - } - // Other message types like Binary, Pong are ignored. - _ => {} - } - } - connection.handle_close().await; -} diff --git a/src/server/socket.rs b/src/server/socket.rs index 6cc825d..6b48154 100644 --- a/src/server/socket.rs +++ b/src/server/socket.rs @@ -1,63 +1,70 @@ use std::sync::Arc; use futures::StreamExt; -use futures::stream::SplitSink; -use futures::stream::SplitStream; -use hyper::upgrade::Upgraded; +use hyper::upgrade::OnUpgrade; use hyper_util::rt::TokioIo; +use tokio_tungstenite::WebSocketStream; use tungstenite::Message; use crate::log; use crate::server::omikron_connection::OmikronConnection; -pub fn handle( - path: String, - writer: SplitSink>, Message>, - reader: SplitStream>>, -) { - log!("handling"); +pub fn handle(path: String, upgrades: OnUpgrade) { tokio::spawn(async move { - if path.starts_with("/ws/phi/") { - } else if path.starts_with("/ws/omikron/") { - let community_conn: Arc = - Arc::from(OmikronConnection::new(writer, reader)); - loop { - let msg_result: Option> = { - let mut session_lock = community_conn.receiver.write().await; - session_lock.next().await - }; + match upgrades.await { + Ok(upgraded_stream) => { + log!("Valid WebSocket upgrade"); + let raw_stream = TokioIo::new(upgraded_stream); - match msg_result { - Some(Ok(msg)) => { - if msg.is_text() { - let text = msg.into_text().unwrap(); - community_conn - .clone() - .handle_message(text.to_string()) - .await; - } else if msg.is_ping() { - let pong_response = crate::data::communication::CommunicationValue::new( - crate::data::communication::CommunicationType::pong, - ); - community_conn.send_message(&pong_response).await; - } else if msg.is_close() { - log!("Closing: {}", msg); - community_conn.handle_close().await; - return; - } - } - Some(Err(e)) => { - log!("Closing ERR: {}", e); - community_conn.handle_close().await; - return; - } - None => { - log!("Closed Session me!"); - community_conn.handle_close().await; - return; - } + let ws_stream = WebSocketStream::from_raw_socket( + raw_stream, + tungstenite::protocol::Role::Server, + None, + ) + .await; + log!( + "WebSocket handshake successful, handling connection for {}", + path + ); + + let (writer, reader) = ws_stream.split(); + if path == "/ws/omikron" { + let connection = OmikronConnection::new(writer, reader); + start_connecteable_handler(connection).await; } } + Err(e) => { + log!("WebSocket upgrade failed after response: {:?}", e); + } } }); } +pub async fn start_connecteable_handler(connection: Arc) { + loop { + let msg = match { + let mut receiver = connection.receiver.write().await; + receiver.next().await + } { + Some(Ok(msg)) => msg, + Some(Err(e)) => { + log!("WS Error: {}", e); + break; + } + _ => break, + }; + + match msg { + Message::Text(text) => { + let conn_clone = connection.clone(); + tokio::spawn(async move { + conn_clone.handle_message(text.to_string()).await; + }); + } + Message::Close(_) => { + break; + } + _ => {} + } + } + connection.handle_close().await; +} diff --git a/src/sql/connection_status.rs b/src/sql/connection_status.rs new file mode 100644 index 0000000..5e7c68f --- /dev/null +++ b/src/sql/connection_status.rs @@ -0,0 +1,38 @@ +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum ConnectionType { + Online, + UserOffline, + IotaOffline, + Away, + DoNotDisturb, +} +impl ConnectionType { + pub fn to_str(&self) -> &str { + match self { + ConnectionType::Online => "online", + ConnectionType::UserOffline => "user_offline", + ConnectionType::IotaOffline => "iota_offline", + ConnectionType::Away => "away", + ConnectionType::DoNotDisturb => "do_not_disturb", + } + } + pub fn to_string(&self) -> String { + match self { + ConnectionType::Online => "online".to_string(), + ConnectionType::UserOffline => "user_offline".to_string(), + ConnectionType::IotaOffline => "iota_offline".to_string(), + ConnectionType::Away => "away".to_string(), + ConnectionType::DoNotDisturb => "do_not_disturb".to_string(), + } + } + pub fn from_str(s: &str) -> Option { + match s.to_lowercase().as_str() { + "online" => Some(ConnectionType::Online), + "user_offline" => Some(ConnectionType::UserOffline), + "iota_offline" => Some(ConnectionType::IotaOffline), + "away" => Some(ConnectionType::Away), + "do_not_disturb" => Some(ConnectionType::DoNotDisturb), + _ => None, + } + } +} diff --git a/src/sql/mod.rs b/src/sql/mod.rs index 583d27a..033fd01 100644 --- a/src/sql/mod.rs +++ b/src/sql/mod.rs @@ -1,3 +1,4 @@ +pub mod connection_status; pub mod iota_omikron_tracker; pub mod sql; pub mod user_online_tracker; diff --git a/src/sql/sql.rs b/src/sql/sql.rs index a5463d7..251ecb8 100644 --- a/src/sql/sql.rs +++ b/src/sql/sql.rs @@ -202,11 +202,11 @@ pub async fn get_by_username( String::from_utf8_lossy(&token).to_string(), )) } - None => Err(sqlx::Error::RowNotFound), + _ => Err(sqlx::Error::RowNotFound), } } -pub async fn get_by_id( +pub async fn get_by_user_id( id: i64, ) -> Result< ( @@ -265,10 +265,73 @@ pub async fn get_by_id( String::from_utf8_lossy(&token).to_string(), )) } - None => Err(sqlx::Error::RowNotFound), + _ => Err(sqlx::Error::RowNotFound), } } +pub async fn get_users_by_iota_id( + iota_id_param: i64, +) -> Result< + Vec<( + i64, + i64, + String, + String, + String, + String, + String, + i32, + i64, + String, + String, + String, + )>, + sqlx::Error, +> { + let db_lock = SQL_DB.read().await; + let pool = db_lock.as_ref().expect("Database pool is not initialized"); + + let rows = sqlx::query( + "SELECT id, iota_id, username, display, status, about, avatar, sub_level, sub_end, public_key, private_key_hash, token FROM users WHERE iota_id = ?", + ) + .bind(iota_id_param) + .fetch_all(pool) + .await?; + + let mut users = Vec::new(); + for row in rows { + let id: i64 = row.get("id"); + let iota_id: i64 = row.get("iota_id"); + let username: String = row.get("username"); + let display: Vec = row.get("display"); + let status: Vec = row.get("status"); + let about: Vec = row.get("about"); + let avatar: Vec = row.get("avatar"); + let sub_level: i32 = row.get("sub_level"); + let sub_end: i64 = row.get("sub_end"); + let public_key: String = row.get("public_key"); + let private_key_hash: String = row.get("private_key_hash"); + let token: Vec = row.get("token"); + + users.push(( + id, + iota_id, + username, + String::from_utf8_lossy(&display).to_string(), + String::from_utf8_lossy(&status).to_string(), + String::from_utf8_lossy(&about).to_string(), + String::from_utf8_lossy(&avatar).to_string(), + sub_level, + sub_end, + public_key, + private_key_hash, + String::from_utf8_lossy(&token).to_string(), + )); + } + + Ok(users) +} + pub async fn change_username(id: i64, new_username: String) -> Result<(), sqlx::Error> { let db_lock = SQL_DB.read().await; let pool = db_lock.as_ref().expect("Database pool is not initialized"); @@ -514,7 +577,7 @@ pub async fn get_random_omikron() -> Result<(i64, String, String), sqlx::Error> String::from_utf8_lossy(&public_key).to_string(), String::from_utf8_lossy(&ip_address).to_string(), )), - None => Err(sqlx::Error::RowNotFound), + _ => Err(sqlx::Error::RowNotFound), } } @@ -534,7 +597,7 @@ pub async fn get_omikron_by_id(id: i64) -> Result<(String, String), sqlx::Error> String::from_utf8_lossy(&public_key).to_string(), String::from_utf8_lossy(&ip_address).to_string(), )), - None => Err(sqlx::Error::RowNotFound), + _ => Err(sqlx::Error::RowNotFound), } } diff --git a/src/sql/user_online_tracker.rs b/src/sql/user_online_tracker.rs index 0cae3fa..95e94c6 100644 --- a/src/sql/user_online_tracker.rs +++ b/src/sql/user_online_tracker.rs @@ -1,27 +1,100 @@ +use crate::sql; +use crate::sql::connection_status::ConnectionType; use once_cell::sync::Lazy; use std::collections::HashMap; use std::sync::Arc; use tokio::sync::RwLock; -static USER_OMIKRON_MAP: Lazy>>> = +#[derive(Debug, Clone)] +pub struct UserStatus { + pub connection_type: ConnectionType, + pub omikron_id: i64, +} + +// IotaID -> Vec +static IOTA_OMIKRON_CONNECTIONS: Lazy>>>> = Lazy::new(|| Arc::new(RwLock::new(HashMap::new()))); -pub async fn track_user_omikron(user: i64, omikron: i64) { - let mut c = USER_OMIKRON_MAP.write().await; - c.insert(user, omikron); +// UserID -> UserStatus +static USER_STATUS_MAP: Lazy>>> = + Lazy::new(|| Arc::new(RwLock::new(HashMap::new()))); + +pub async fn track_iota_connection(iota_id: i64, omikron_id: i64) { + let mut iota_map = IOTA_OMIKRON_CONNECTIONS.write().await; + let connections = iota_map.entry(iota_id).or_default(); + if !connections.contains(&omikron_id) { + connections.push(omikron_id); + } } -pub async fn get_omikron_for_user(user: i64) -> Option { - let c = USER_OMIKRON_MAP.read().await; - c.get(&user).cloned() +pub async fn untrack_iota_connection(iota_id: i64, omikron_id: i64) -> bool { + let mut iota_map = IOTA_OMIKRON_CONNECTIONS.write().await; + if let Some(connections) = iota_map.get_mut(&iota_id) { + connections.retain(|&id| id != omikron_id); + if connections.is_empty() { + iota_map.remove(&iota_id); + return true; // Iota is now offline + } + } + false } -pub async fn untrack_user(user: i64) { - let mut c = USER_OMIKRON_MAP.write().await; - c.remove(&user); +pub async fn get_iota_omikron_connections(iota_id: i64) -> Option> { + let iota_map = IOTA_OMIKRON_CONNECTIONS.read().await; + iota_map.get(&iota_id).cloned() } -pub async fn untrack_by_omikron(omikron: i64) { - let mut c = USER_OMIKRON_MAP.write().await; - c.retain(|_, v| *v != omikron); +pub async fn track_user_status(user_id: i64, status: ConnectionType, omikron_id: i64) { + let mut user_map = USER_STATUS_MAP.write().await; + user_map.insert( + user_id, + UserStatus { + connection_type: status, + omikron_id, + }, + ); +} + +pub async fn get_user_status(user_id: i64) -> Option { + let user_map = USER_STATUS_MAP.read().await; + user_map.get(&user_id).cloned() +} + +pub async fn untrack_user(user_id: i64) { + let mut user_map = USER_STATUS_MAP.write().await; + user_map.remove(&user_id); +} + +pub async fn untrack_many_users(user_ids: &[i64]) { + let mut user_map = USER_STATUS_MAP.write().await; + for user_id in user_ids { + user_map.remove(user_id); + } +} + +pub async fn untrack_omikron(omikron_id: i64) { + let mut iota_map = IOTA_OMIKRON_CONNECTIONS.write().await; + let mut user_map = USER_STATUS_MAP.write().await; + + let mut offline_iotas = Vec::new(); + + iota_map.retain(|iota_id, connections| { + connections.retain(|id| *id != omikron_id); + if connections.is_empty() { + offline_iotas.push(*iota_id); + false + } else { + true + } + }); + + user_map.retain(|_, status| status.omikron_id != omikron_id); + + for iota_id in offline_iotas { + if let Ok(users) = sql::sql::get_users_by_iota_id(iota_id).await { + for user in users { + user_map.remove(&user.0); + } + } + } }