Format, Status, User Stati & Endpoints

This commit is contained in:
Alex Emmet 2026-01-07 15:54:05 +01:00
commit 97f0a76c6c
11 changed files with 963 additions and 396 deletions

260
Cargo.lock generated
View file

@ -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"

View file

@ -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"

View file

@ -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,
}
}

View file

@ -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/<username>
"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);

View file

@ -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<i64> = 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<Self>) {
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;
}
}
}

View file

@ -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<HttpRequest<Incoming>> 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<HttpRequest<Incoming>> 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<ServerConfig>) -> 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<ServerConfig>) -> 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<ServerConfig>) -> 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<Option<Arc<ServerConfig>>, Box<dyn Error>> {
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<Option<Arc<ServerConfig>>, Box<dyn Error>> {
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<Option<Arc<ServerConfig>>, Box<dyn Error>> {
// 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::<Result<Vec<PrivateKeyDer>, 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::<Result<Vec<PrivateKeyDer>, io::Error>>()?;
@ -424,14 +382,14 @@ fn load_tls_config() -> Result<Option<Arc<ServerConfig>>, Box<dyn Error>> {
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::<Result<Vec<PrivateKeyDer>, 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<Option<Arc<ServerConfig>>, Box<dyn Error>> {
Ok(Some(Arc::new(config)))
}
// In deiner Server-Logik, wo OmikronConnection initialisiert wird:
pub async fn start_omikron_handler(connection: Arc<OmikronConnection>) {
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;
}

View file

@ -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<tokio_tungstenite::WebSocketStream<TokioIo<Upgraded>>, Message>,
reader: SplitStream<tokio_tungstenite::WebSocketStream<TokioIo<Upgraded>>>,
) {
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<OmikronConnection> =
Arc::from(OmikronConnection::new(writer, reader));
loop {
let msg_result: Option<Result<_, _>> = {
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<OmikronConnection>) {
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;
}

View file

@ -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<ConnectionType> {
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,
}
}
}

View file

@ -1,3 +1,4 @@
pub mod connection_status;
pub mod iota_omikron_tracker;
pub mod sql;
pub mod user_online_tracker;

View file

@ -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<u8> = row.get("display");
let status: Vec<u8> = row.get("status");
let about: Vec<u8> = row.get("about");
let avatar: Vec<u8> = 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<u8> = 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),
}
}

View file

@ -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<Arc<RwLock<HashMap<i64, i64>>>> =
#[derive(Debug, Clone)]
pub struct UserStatus {
pub connection_type: ConnectionType,
pub omikron_id: i64,
}
// IotaID -> Vec<OmikronID>
static IOTA_OMIKRON_CONNECTIONS: Lazy<Arc<RwLock<HashMap<i64, Vec<i64>>>>> =
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<Arc<RwLock<HashMap<i64, UserStatus>>>> =
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<i64> {
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<Vec<i64>> {
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<UserStatus> {
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);
}
}
}
}