mirror of
https://github.com/Blah-IM/blahrs.git
synced 2025-05-01 00:31:09 +00:00
Switch room identifier from UUID to stringified i64
This commit is contained in:
parent
236fb61832
commit
59d51937da
11 changed files with 381 additions and 143 deletions
248
Cargo.lock
generated
248
Cargo.lock
generated
|
@ -29,6 +29,21 @@ dependencies = [
|
||||||
"zerocopy",
|
"zerocopy",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "android-tzdata"
|
||||||
|
version = "0.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "android_system_properties"
|
||||||
|
version = "0.1.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "anstream"
|
name = "anstream"
|
||||||
version = "0.6.15"
|
version = "0.6.15"
|
||||||
|
@ -251,6 +266,7 @@ dependencies = [
|
||||||
"bitflags",
|
"bitflags",
|
||||||
"bitflags_serde_shim",
|
"bitflags_serde_shim",
|
||||||
"ed25519-dalek",
|
"ed25519-dalek",
|
||||||
|
"expect-test",
|
||||||
"hex",
|
"hex",
|
||||||
"html-escape",
|
"html-escape",
|
||||||
"rand",
|
"rand",
|
||||||
|
@ -259,7 +275,7 @@ dependencies = [
|
||||||
"serde",
|
"serde",
|
||||||
"serde_jcs",
|
"serde_jcs",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"uuid",
|
"serde_with",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -304,7 +320,6 @@ dependencies = [
|
||||||
"tracing",
|
"tracing",
|
||||||
"tracing-subscriber",
|
"tracing-subscriber",
|
||||||
"url",
|
"url",
|
||||||
"uuid",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -349,6 +364,19 @@ version = "1.0.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "chrono"
|
||||||
|
version = "0.4.38"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401"
|
||||||
|
dependencies = [
|
||||||
|
"android-tzdata",
|
||||||
|
"iana-time-zone",
|
||||||
|
"num-traits",
|
||||||
|
"serde",
|
||||||
|
"windows-targets",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "clap"
|
name = "clap"
|
||||||
version = "4.5.16"
|
version = "4.5.16"
|
||||||
|
@ -463,6 +491,41 @@ dependencies = [
|
||||||
"syn",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "darling"
|
||||||
|
version = "0.20.10"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6f63b86c8a8826a49b8c21f08a2d07338eec8d900540f8630dc76284be802989"
|
||||||
|
dependencies = [
|
||||||
|
"darling_core",
|
||||||
|
"darling_macro",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "darling_core"
|
||||||
|
version = "0.20.10"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "95133861a8032aaea082871032f5815eb9e98cef03fa916ab4500513994df9e5"
|
||||||
|
dependencies = [
|
||||||
|
"fnv",
|
||||||
|
"ident_case",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"strsim",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "darling_macro"
|
||||||
|
version = "0.20.10"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806"
|
||||||
|
dependencies = [
|
||||||
|
"darling_core",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "data-encoding"
|
name = "data-encoding"
|
||||||
version = "2.6.0"
|
version = "2.6.0"
|
||||||
|
@ -480,6 +543,16 @@ dependencies = [
|
||||||
"zeroize",
|
"zeroize",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "deranged"
|
||||||
|
version = "0.3.11"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4"
|
||||||
|
dependencies = [
|
||||||
|
"powerfmt",
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "digest"
|
name = "digest"
|
||||||
version = "0.10.7"
|
version = "0.10.7"
|
||||||
|
@ -490,6 +563,12 @@ dependencies = [
|
||||||
"crypto-common",
|
"crypto-common",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "dissimilar"
|
||||||
|
version = "1.0.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "59f8e79d1fbf76bdfbde321e902714bf6c49df88a7dda6fc682fc2979226962d"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ed25519"
|
name = "ed25519"
|
||||||
version = "2.2.3"
|
version = "2.2.3"
|
||||||
|
@ -540,6 +619,16 @@ dependencies = [
|
||||||
"windows-sys 0.52.0",
|
"windows-sys 0.52.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "expect-test"
|
||||||
|
version = "1.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9e0be0a561335815e06dab7c62e50353134c796e7a6155402a64bcff66b6a5e0"
|
||||||
|
dependencies = [
|
||||||
|
"dissimilar",
|
||||||
|
"once_cell",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "fallible-iterator"
|
name = "fallible-iterator"
|
||||||
version = "0.3.0"
|
version = "0.3.0"
|
||||||
|
@ -686,13 +775,19 @@ dependencies = [
|
||||||
"futures-core",
|
"futures-core",
|
||||||
"futures-sink",
|
"futures-sink",
|
||||||
"http",
|
"http",
|
||||||
"indexmap",
|
"indexmap 2.4.0",
|
||||||
"slab",
|
"slab",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tokio-util",
|
"tokio-util",
|
||||||
"tracing",
|
"tracing",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hashbrown"
|
||||||
|
version = "0.12.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hashbrown"
|
name = "hashbrown"
|
||||||
version = "0.14.5"
|
version = "0.14.5"
|
||||||
|
@ -708,7 +803,7 @@ version = "0.9.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6ba4ff7128dee98c7dc9794b6a411377e1404dba1c97deb8d1a55297bd25d8af"
|
checksum = "6ba4ff7128dee98c7dc9794b6a411377e1404dba1c97deb8d1a55297bd25d8af"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"hashbrown",
|
"hashbrown 0.14.5",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -867,6 +962,35 @@ dependencies = [
|
||||||
"tracing",
|
"tracing",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "iana-time-zone"
|
||||||
|
version = "0.1.60"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e7ffbb5a1b541ea2561f8c41c087286cc091e21e556a4f09a8f6cbf17b69b141"
|
||||||
|
dependencies = [
|
||||||
|
"android_system_properties",
|
||||||
|
"core-foundation-sys",
|
||||||
|
"iana-time-zone-haiku",
|
||||||
|
"js-sys",
|
||||||
|
"wasm-bindgen",
|
||||||
|
"windows-core",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "iana-time-zone-haiku"
|
||||||
|
version = "0.1.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f"
|
||||||
|
dependencies = [
|
||||||
|
"cc",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ident_case"
|
||||||
|
version = "1.0.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "idna"
|
name = "idna"
|
||||||
version = "0.5.0"
|
version = "0.5.0"
|
||||||
|
@ -877,6 +1001,17 @@ dependencies = [
|
||||||
"unicode-normalization",
|
"unicode-normalization",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "indexmap"
|
||||||
|
version = "1.9.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99"
|
||||||
|
dependencies = [
|
||||||
|
"autocfg",
|
||||||
|
"hashbrown 0.12.3",
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "indexmap"
|
name = "indexmap"
|
||||||
version = "2.4.0"
|
version = "2.4.0"
|
||||||
|
@ -884,7 +1019,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "93ead53efc7ea8ed3cfb0c79fc8023fbb782a5432b52830b6518941cebe6505c"
|
checksum = "93ead53efc7ea8ed3cfb0c79fc8023fbb782a5432b52830b6518941cebe6505c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"equivalent",
|
"equivalent",
|
||||||
"hashbrown",
|
"hashbrown 0.14.5",
|
||||||
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -1024,6 +1160,21 @@ dependencies = [
|
||||||
"winapi",
|
"winapi",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "num-conv"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "num-traits"
|
||||||
|
version = "0.2.19"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
|
||||||
|
dependencies = [
|
||||||
|
"autocfg",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "object"
|
name = "object"
|
||||||
version = "0.36.3"
|
version = "0.36.3"
|
||||||
|
@ -1175,6 +1326,12 @@ version = "0.3.30"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec"
|
checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "powerfmt"
|
||||||
|
version = "0.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ppv-lite86"
|
name = "ppv-lite86"
|
||||||
version = "0.2.20"
|
version = "0.2.20"
|
||||||
|
@ -1311,7 +1468,6 @@ dependencies = [
|
||||||
"hashlink",
|
"hashlink",
|
||||||
"libsqlite3-sys",
|
"libsqlite3-sys",
|
||||||
"smallvec",
|
"smallvec",
|
||||||
"uuid",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -1526,6 +1682,36 @@ dependencies = [
|
||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde_with"
|
||||||
|
version = "3.9.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "69cecfa94848272156ea67b2b1a53f20fc7bc638c4a46d2f8abde08f05f4b857"
|
||||||
|
dependencies = [
|
||||||
|
"base64 0.22.1",
|
||||||
|
"chrono",
|
||||||
|
"hex",
|
||||||
|
"indexmap 1.9.3",
|
||||||
|
"indexmap 2.4.0",
|
||||||
|
"serde",
|
||||||
|
"serde_derive",
|
||||||
|
"serde_json",
|
||||||
|
"serde_with_macros",
|
||||||
|
"time",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde_with_macros"
|
||||||
|
version = "3.9.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a8fee4991ef4f274617a51ad4af30519438dacb2f56ac773b08a1922ff743350"
|
||||||
|
dependencies = [
|
||||||
|
"darling",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "sha1"
|
name = "sha1"
|
||||||
version = "0.10.6"
|
version = "0.10.6"
|
||||||
|
@ -1715,6 +1901,37 @@ dependencies = [
|
||||||
"once_cell",
|
"once_cell",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "time"
|
||||||
|
version = "0.3.36"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885"
|
||||||
|
dependencies = [
|
||||||
|
"deranged",
|
||||||
|
"itoa",
|
||||||
|
"num-conv",
|
||||||
|
"powerfmt",
|
||||||
|
"serde",
|
||||||
|
"time-core",
|
||||||
|
"time-macros",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "time-core"
|
||||||
|
version = "0.1.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "time-macros"
|
||||||
|
version = "0.2.18"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf"
|
||||||
|
dependencies = [
|
||||||
|
"num-conv",
|
||||||
|
"time-core",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tinyvec"
|
name = "tinyvec"
|
||||||
version = "1.8.0"
|
version = "1.8.0"
|
||||||
|
@ -2005,16 +2222,6 @@ version = "0.2.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
|
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "uuid"
|
|
||||||
version = "1.10.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "81dfa00651efa65069b0b6b651f4aaa31ba9e3c3ce0137aaad053604ee7e0314"
|
|
||||||
dependencies = [
|
|
||||||
"getrandom",
|
|
||||||
"serde",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "valuable"
|
name = "valuable"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
@ -2147,6 +2354,15 @@ version = "0.4.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-core"
|
||||||
|
version = "0.52.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9"
|
||||||
|
dependencies = [
|
||||||
|
"windows-targets",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows-registry"
|
name = "windows-registry"
|
||||||
version = "0.2.0"
|
version = "0.2.0"
|
||||||
|
|
|
@ -31,7 +31,10 @@ rusqlite = { version = "0.32", optional = true }
|
||||||
serde = { version = "1", features = ["derive"] }
|
serde = { version = "1", features = ["derive"] }
|
||||||
serde_jcs = "0.1"
|
serde_jcs = "0.1"
|
||||||
serde_json = "1"
|
serde_json = "1"
|
||||||
uuid = { version = "1", features = ["serde"] }
|
serde_with = "3.9.0"
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
expect-test = "1.5.0"
|
||||||
|
|
||||||
[lints]
|
[lints]
|
||||||
workspace = true
|
workspace = true
|
||||||
|
|
|
@ -5,10 +5,9 @@ use std::{fs, io};
|
||||||
use anyhow::{Context, Result};
|
use anyhow::{Context, Result};
|
||||||
use blah::bitflags;
|
use blah::bitflags;
|
||||||
use blah::types::{
|
use blah::types::{
|
||||||
get_timestamp, ChatPayload, CreateRoomPayload, MemberPermission, RichText, RoomAttrs,
|
get_timestamp, ChatPayload, CreateRoomPayload, Id, MemberPermission, RichText, RoomAttrs,
|
||||||
RoomMember, RoomMemberList, ServerPermission, UserKey, WithSig,
|
RoomMember, RoomMemberList, ServerPermission, UserKey, WithSig,
|
||||||
};
|
};
|
||||||
use blah::uuid::Uuid;
|
|
||||||
use ed25519_dalek::pkcs8::spki::der::pem::LineEnding;
|
use ed25519_dalek::pkcs8::spki::der::pem::LineEnding;
|
||||||
use ed25519_dalek::pkcs8::{DecodePrivateKey, DecodePublicKey, EncodePrivateKey, EncodePublicKey};
|
use ed25519_dalek::pkcs8::{DecodePrivateKey, DecodePublicKey, EncodePrivateKey, EncodePublicKey};
|
||||||
use ed25519_dalek::{SigningKey, VerifyingKey, PUBLIC_KEY_LENGTH};
|
use ed25519_dalek::{SigningKey, VerifyingKey, PUBLIC_KEY_LENGTH};
|
||||||
|
@ -94,7 +93,7 @@ enum ApiCommand {
|
||||||
private_key_file: PathBuf,
|
private_key_file: PathBuf,
|
||||||
|
|
||||||
#[arg(long)]
|
#[arg(long)]
|
||||||
room: Uuid,
|
room: i64,
|
||||||
|
|
||||||
#[arg(long)]
|
#[arg(long)]
|
||||||
text: String,
|
text: String,
|
||||||
|
@ -249,7 +248,7 @@ async fn main_api(api_url: Url, command: ApiCommand) -> Result<()> {
|
||||||
} => {
|
} => {
|
||||||
let key = load_signing_key(&private_key_file)?;
|
let key = load_signing_key(&private_key_file)?;
|
||||||
let payload = ChatPayload {
|
let payload = ChatPayload {
|
||||||
room,
|
room: Id(room),
|
||||||
rich_text: RichText::from(text),
|
rich_text: RichText::from(text),
|
||||||
};
|
};
|
||||||
let payload = WithSig::sign(&key, get_timestamp(), &mut OsRng, payload)?;
|
let payload = WithSig::sign(&key, get_timestamp(), &mut OsRng, payload)?;
|
||||||
|
|
|
@ -14,7 +14,7 @@ futures-util = "0.3"
|
||||||
hex = { version = "0.4", features = ["serde"] }
|
hex = { version = "0.4", features = ["serde"] }
|
||||||
humantime = "2"
|
humantime = "2"
|
||||||
parking_lot = "0.12" # Maybe no better performance, just that we hate poisoning. ¯\_(ツ)_/¯
|
parking_lot = "0.12" # Maybe no better performance, just that we hate poisoning. ¯\_(ツ)_/¯
|
||||||
rusqlite = { version = "0.32", features = ["uuid"] }
|
rusqlite = "0.32"
|
||||||
sd-notify = "0.4"
|
sd-notify = "0.4"
|
||||||
serde = { version = "1", features = ["derive"] }
|
serde = { version = "1", features = ["derive"] }
|
||||||
serde-inline-default = "0.2.0"
|
serde-inline-default = "0.2.0"
|
||||||
|
@ -26,7 +26,6 @@ tower-http = { version = "0.5", features = ["cors", "limit"] }
|
||||||
tracing = "0.1"
|
tracing = "0.1"
|
||||||
tracing-subscriber = "0.3"
|
tracing-subscriber = "0.3"
|
||||||
url = { version = "2.5.2", features = ["serde"] }
|
url = { version = "2.5.2", features = ["serde"] }
|
||||||
uuid = { version = "1", features = ["v4"] }
|
|
||||||
|
|
||||||
blah = { path = "..", features = ["rusqlite"] }
|
blah = { path = "..", features = ["rusqlite"] }
|
||||||
|
|
||||||
|
|
|
@ -82,14 +82,14 @@ paths:
|
||||||
content:
|
content:
|
||||||
application/json:
|
application/json:
|
||||||
type: string
|
type: string
|
||||||
description: UUID of the newly created room (ruuid).
|
description: Id of the newly created room (rid).
|
||||||
403:
|
403:
|
||||||
description: The user does not have permission to create room.
|
description: The user does not have permission to create room.
|
||||||
content:
|
content:
|
||||||
application/json:
|
application/json:
|
||||||
$ref: '#/components/schemas/ApiError'
|
$ref: '#/components/schemas/ApiError'
|
||||||
|
|
||||||
/room/{ruuid}:
|
/room/{rid}:
|
||||||
get:
|
get:
|
||||||
summary: Get room metadata
|
summary: Get room metadata
|
||||||
responses:
|
responses:
|
||||||
|
@ -107,9 +107,9 @@ paths:
|
||||||
$ref: '#/components/schemas/ApiError'
|
$ref: '#/components/schemas/ApiError'
|
||||||
|
|
||||||
|
|
||||||
/room/{ruuid}/feed.json:
|
/room/{rid}/feed.json:
|
||||||
get:
|
get:
|
||||||
summary: JSON feed of room {ruuid}, which must be public readable
|
summary: JSON feed of room {rid}, which must be public readable
|
||||||
description: For human and feed reader consumption only.
|
description: For human and feed reader consumption only.
|
||||||
responses:
|
responses:
|
||||||
200:
|
200:
|
||||||
|
@ -122,9 +122,9 @@ paths:
|
||||||
application/json:
|
application/json:
|
||||||
$ref: '#/components/schemas/ApiError'
|
$ref: '#/components/schemas/ApiError'
|
||||||
|
|
||||||
/room/{ruuid}/item:
|
/room/{rid}/item:
|
||||||
get:
|
get:
|
||||||
summary: Get chat history for room {ruuid}
|
summary: Get chat history for room {rid}
|
||||||
description: |
|
description: |
|
||||||
Return chat items in reversed time order, up to PAGE_LEN items.
|
Return chat items in reversed time order, up to PAGE_LEN items.
|
||||||
The last (oldest) chat id can be used as query parameter for the next
|
The last (oldest) chat id can be used as query parameter for the next
|
||||||
|
@ -159,7 +159,7 @@ paths:
|
||||||
$ref: '#/components/schemas/ApiError'
|
$ref: '#/components/schemas/ApiError'
|
||||||
|
|
||||||
post:
|
post:
|
||||||
summary: Post a chat in room {ruuid}
|
summary: Post a chat in room {rid}
|
||||||
requestBody:
|
requestBody:
|
||||||
content:
|
content:
|
||||||
application/json:
|
application/json:
|
||||||
|
@ -193,7 +193,7 @@ paths:
|
||||||
application/json:
|
application/json:
|
||||||
$ref: '#/components/schemas/ApiError'
|
$ref: '#/components/schemas/ApiError'
|
||||||
|
|
||||||
/room/{ruuid}/admin:
|
/room/{rid}/admin:
|
||||||
post:
|
post:
|
||||||
summary: Room management
|
summary: Room management
|
||||||
requestBody:
|
requestBody:
|
||||||
|
@ -251,9 +251,9 @@ components:
|
||||||
|
|
||||||
RoomMetadataForList:
|
RoomMetadataForList:
|
||||||
type: object
|
type: object
|
||||||
required: ['ruuid', 'title', 'attrs']
|
required: ['rid', 'title', 'attrs']
|
||||||
properties:
|
properties:
|
||||||
ruuid:
|
rid:
|
||||||
type: string
|
type: string
|
||||||
title:
|
title:
|
||||||
type: string
|
type: string
|
||||||
|
@ -265,7 +265,7 @@ components:
|
||||||
RoomMetadata:
|
RoomMetadata:
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
ruuid:
|
rid:
|
||||||
type: string
|
type: string
|
||||||
title:
|
title:
|
||||||
type: string
|
type: string
|
||||||
|
|
|
@ -8,8 +8,7 @@ CREATE TABLE IF NOT EXISTS `user` (
|
||||||
) STRICT;
|
) STRICT;
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS `room` (
|
CREATE TABLE IF NOT EXISTS `room` (
|
||||||
`rid` INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
`rid` INTEGER NOT NULL PRIMARY KEY,
|
||||||
`ruuid` BLOB NOT NULL UNIQUE,
|
|
||||||
`title` TEXT NOT NULL,
|
`title` TEXT NOT NULL,
|
||||||
`attrs` INTEGER NOT NULL
|
`attrs` INTEGER NOT NULL
|
||||||
) STRICT;
|
) STRICT;
|
||||||
|
|
|
@ -10,7 +10,7 @@ static INIT_SQL: &str = include_str!("../schema.sql");
|
||||||
|
|
||||||
// Simple and stupid version check for now.
|
// Simple and stupid version check for now.
|
||||||
// `echo -n 'blahd-database-0' | sha256sum | head -c5` || version
|
// `echo -n 'blahd-database-0' | sha256sum | head -c5` || version
|
||||||
const APPLICATION_ID: i32 = 0xd9e_8400;
|
const APPLICATION_ID: i32 = 0xd9e_8401;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Database {
|
pub struct Database {
|
||||||
|
|
|
@ -12,7 +12,7 @@ use axum::routing::{get, post};
|
||||||
use axum::{Json, Router};
|
use axum::{Json, Router};
|
||||||
use axum_extra::extract::WithRejection as R;
|
use axum_extra::extract::WithRejection as R;
|
||||||
use blah::types::{
|
use blah::types::{
|
||||||
ChatItem, ChatPayload, CreateRoomPayload, MemberPermission, RoomAdminOp, RoomAdminPayload,
|
ChatItem, ChatPayload, CreateRoomPayload, Id, MemberPermission, RoomAdminOp, RoomAdminPayload,
|
||||||
RoomAttrs, ServerPermission, Signee, UserKey, WithSig,
|
RoomAttrs, ServerPermission, Signee, UserKey, WithSig,
|
||||||
};
|
};
|
||||||
use config::Config;
|
use config::Config;
|
||||||
|
@ -24,7 +24,6 @@ use rusqlite::{named_params, params, Connection, OptionalExtension, Row, ToSql};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use url::Url;
|
use url::Url;
|
||||||
use utils::ExpiringSet;
|
use utils::ExpiringSet;
|
||||||
use uuid::Uuid;
|
|
||||||
|
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
mod middleware;
|
mod middleware;
|
||||||
|
@ -143,11 +142,11 @@ async fn main_async(st: AppState) -> Result<()> {
|
||||||
.route("/ws", get(handle_ws))
|
.route("/ws", get(handle_ws))
|
||||||
.route("/room", get(room_list))
|
.route("/room", get(room_list))
|
||||||
.route("/room/create", post(room_create))
|
.route("/room/create", post(room_create))
|
||||||
.route("/room/:ruuid", get(room_get_metadata))
|
.route("/room/:rid", get(room_get_metadata))
|
||||||
// NB. Sync with `feed_url` and `next_url` generation.
|
// NB. Sync with `feed_url` and `next_url` generation.
|
||||||
.route("/room/:ruuid/feed.json", get(room_get_feed))
|
.route("/room/:rid/feed.json", get(room_get_feed))
|
||||||
.route("/room/:ruuid/item", get(room_get_item).post(room_post_item))
|
.route("/room/:rid/item", get(room_get_item).post(room_post_item))
|
||||||
.route("/room/:ruuid/admin", post(room_admin))
|
.route("/room/:rid/admin", post(room_admin))
|
||||||
.with_state(st.clone())
|
.with_state(st.clone())
|
||||||
.layer(tower_http::limit::RequestBodyLimitLayer::new(
|
.layer(tower_http::limit::RequestBodyLimitLayer::new(
|
||||||
st.config.server.max_request_len,
|
st.config.server.max_request_len,
|
||||||
|
@ -195,7 +194,7 @@ async fn handle_ws(State(st): ArcState, ws: WebSocketUpgrade) -> Response {
|
||||||
struct RoomList {
|
struct RoomList {
|
||||||
rooms: Vec<RoomMetadata>,
|
rooms: Vec<RoomMetadata>,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
skip_token: Option<u64>,
|
skip_token: Option<Id>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
|
@ -235,8 +234,8 @@ async fn room_list(
|
||||||
.prepare(sql)?
|
.prepare(sql)?
|
||||||
.query_map(params, |row| {
|
.query_map(params, |row| {
|
||||||
// TODO: Extract this into a function.
|
// TODO: Extract this into a function.
|
||||||
last_rid = Some(row.get::<_, u64>("rid")?);
|
let rid = row.get("rid")?;
|
||||||
let ruuid = row.get("ruuid")?;
|
last_rid = Some(rid);
|
||||||
let title = row.get("title")?;
|
let title = row.get("title")?;
|
||||||
let attrs = row.get("attrs")?;
|
let attrs = row.get("attrs")?;
|
||||||
let last_chat = row
|
let last_chat = row
|
||||||
|
@ -250,14 +249,14 @@ async fn room_list(
|
||||||
user,
|
user,
|
||||||
payload: ChatPayload {
|
payload: ChatPayload {
|
||||||
rich_text: row.get("rich_text")?,
|
rich_text: row.get("rich_text")?,
|
||||||
room: ruuid,
|
room: rid,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.transpose()?;
|
.transpose()?;
|
||||||
Ok(RoomMetadata {
|
Ok(RoomMetadata {
|
||||||
ruuid,
|
rid,
|
||||||
title,
|
title,
|
||||||
attrs,
|
attrs,
|
||||||
last_chat,
|
last_chat,
|
||||||
|
@ -271,7 +270,7 @@ async fn room_list(
|
||||||
match params.filter {
|
match params.filter {
|
||||||
ListRoomFilter::Public => query(
|
ListRoomFilter::Public => query(
|
||||||
r"
|
r"
|
||||||
SELECT `rid`, `ruuid`, `title`, `attrs`,
|
SELECT `rid`, `title`, `attrs`,
|
||||||
`last_author`.`userkey` AS `userkey`, `timestamp`, `nonce`, `sig`, `rich_text`
|
`last_author`.`userkey` AS `userkey`, `timestamp`, `nonce`, `sig`, `rich_text`
|
||||||
FROM `room`
|
FROM `room`
|
||||||
LEFT JOIN `room_item` USING (`rid`)
|
LEFT JOIN `room_item` USING (`rid`)
|
||||||
|
@ -293,7 +292,7 @@ async fn room_list(
|
||||||
query(
|
query(
|
||||||
r"
|
r"
|
||||||
SELECT
|
SELECT
|
||||||
`rid`, `ruuid`, `title`, `attrs`,
|
`rid`, `title`, `attrs`,
|
||||||
`last_author`.`userkey`, `timestamp`, `nonce`, `sig`, `rich_text`
|
`last_author`.`userkey`, `timestamp`, `nonce`, `sig`, `rich_text`
|
||||||
FROM `user`
|
FROM `user`
|
||||||
JOIN `room_member` USING (`uid`)
|
JOIN `room_member` USING (`uid`)
|
||||||
|
@ -320,7 +319,7 @@ async fn room_list(
|
||||||
async fn room_create(
|
async fn room_create(
|
||||||
st: ArcState,
|
st: ArcState,
|
||||||
SignedJson(params): SignedJson<CreateRoomPayload>,
|
SignedJson(params): SignedJson<CreateRoomPayload>,
|
||||||
) -> Result<Json<Uuid>, ApiError> {
|
) -> Result<Json<Id>, ApiError> {
|
||||||
let members = ¶ms.signee.payload.members.0;
|
let members = ¶ms.signee.payload.members.0;
|
||||||
if !members
|
if !members
|
||||||
.iter()
|
.iter()
|
||||||
|
@ -356,21 +355,18 @@ async fn room_create(
|
||||||
));
|
));
|
||||||
};
|
};
|
||||||
|
|
||||||
let ruuid = Uuid::new_v4();
|
|
||||||
|
|
||||||
let txn = conn.transaction()?;
|
let txn = conn.transaction()?;
|
||||||
txn.execute(
|
txn.execute(
|
||||||
r"
|
r"
|
||||||
INSERT INTO `room` (`ruuid`, `title`, `attrs`)
|
INSERT INTO `room` (`title`, `attrs`)
|
||||||
VALUES (:ruuid, :title, :attrs)
|
VALUES (:title, :attrs)
|
||||||
",
|
",
|
||||||
named_params! {
|
named_params! {
|
||||||
":ruuid": ruuid,
|
|
||||||
":title": params.signee.payload.title,
|
":title": params.signee.payload.title,
|
||||||
":attrs": params.signee.payload.attrs,
|
":attrs": params.signee.payload.attrs,
|
||||||
},
|
},
|
||||||
)?;
|
)?;
|
||||||
let rid = txn.last_insert_rowid() as u64;
|
let rid = Id(txn.last_insert_rowid());
|
||||||
let mut insert_user = txn.prepare(
|
let mut insert_user = txn.prepare(
|
||||||
r"
|
r"
|
||||||
INSERT INTO `user` (`userkey`)
|
INSERT INTO `user` (`userkey`)
|
||||||
|
@ -398,7 +394,7 @@ async fn room_create(
|
||||||
drop(insert_user);
|
drop(insert_user);
|
||||||
txn.commit()?;
|
txn.commit()?;
|
||||||
|
|
||||||
Ok(Json(ruuid))
|
Ok(Json(rid))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Pagination query parameters.
|
/// Pagination query parameters.
|
||||||
|
@ -432,14 +428,14 @@ struct RoomItems {
|
||||||
|
|
||||||
async fn room_get_item(
|
async fn room_get_item(
|
||||||
st: ArcState,
|
st: ArcState,
|
||||||
R(Path(ruuid), _): RE<Path<Uuid>>,
|
R(Path(rid), _): RE<Path<Id>>,
|
||||||
R(Query(pagination), _): RE<Query<Pagination>>,
|
R(Query(pagination), _): RE<Query<Pagination>>,
|
||||||
auth: MaybeAuth,
|
auth: MaybeAuth,
|
||||||
) -> Result<Json<RoomItems>, ApiError> {
|
) -> Result<Json<RoomItems>, ApiError> {
|
||||||
let (items, skip_token) = {
|
let (items, skip_token) = {
|
||||||
let conn = st.db.get();
|
let conn = st.db.get();
|
||||||
get_room_if_readable(&conn, ruuid, auth.into_optional()?.as_ref(), |_row| Ok(()))?;
|
get_room_if_readable(&conn, rid, auth.into_optional()?.as_ref(), |_row| Ok(()))?;
|
||||||
query_room_items(&st, &conn, ruuid, pagination)?
|
query_room_items(&st, &conn, rid, pagination)?
|
||||||
};
|
};
|
||||||
let items = items.into_iter().map(|(_, item)| item).collect();
|
let items = items.into_iter().map(|(_, item)| item).collect();
|
||||||
Ok(Json(RoomItems {
|
Ok(Json(RoomItems {
|
||||||
|
@ -450,11 +446,11 @@ async fn room_get_item(
|
||||||
|
|
||||||
async fn room_get_metadata(
|
async fn room_get_metadata(
|
||||||
st: ArcState,
|
st: ArcState,
|
||||||
R(Path(ruuid), _): RE<Path<Uuid>>,
|
R(Path(rid), _): RE<Path<Id>>,
|
||||||
auth: MaybeAuth,
|
auth: MaybeAuth,
|
||||||
) -> Result<Json<RoomMetadata>, ApiError> {
|
) -> Result<Json<RoomMetadata>, ApiError> {
|
||||||
let (title, attrs) =
|
let (title, attrs) =
|
||||||
get_room_if_readable(&st.db.get(), ruuid, auth.into_optional()?.as_ref(), |row| {
|
get_room_if_readable(&st.db.get(), rid, auth.into_optional()?.as_ref(), |row| {
|
||||||
Ok((
|
Ok((
|
||||||
row.get::<_, String>("title")?,
|
row.get::<_, String>("title")?,
|
||||||
row.get::<_, RoomAttrs>("attrs")?,
|
row.get::<_, RoomAttrs>("attrs")?,
|
||||||
|
@ -462,7 +458,7 @@ async fn room_get_metadata(
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
Ok(Json(RoomMetadata {
|
Ok(Json(RoomMetadata {
|
||||||
ruuid,
|
rid,
|
||||||
title,
|
title,
|
||||||
attrs,
|
attrs,
|
||||||
last_chat: None,
|
last_chat: None,
|
||||||
|
@ -471,14 +467,14 @@ async fn room_get_metadata(
|
||||||
|
|
||||||
async fn room_get_feed(
|
async fn room_get_feed(
|
||||||
st: ArcState,
|
st: ArcState,
|
||||||
R(Path(ruuid), _): RE<Path<Uuid>>,
|
R(Path(rid), _): RE<Path<Id>>,
|
||||||
R(Query(pagination), _): RE<Query<Pagination>>,
|
R(Query(pagination), _): RE<Query<Pagination>>,
|
||||||
) -> Result<impl IntoResponse, ApiError> {
|
) -> Result<impl IntoResponse, ApiError> {
|
||||||
let title;
|
let title;
|
||||||
let (items, skip_token) = {
|
let (items, skip_token) = {
|
||||||
let conn = st.db.get();
|
let conn = st.db.get();
|
||||||
title = get_room_if_readable(&conn, ruuid, None, |row| row.get::<_, String>("title"))?;
|
title = get_room_if_readable(&conn, rid, None, |row| row.get::<_, String>("title"))?;
|
||||||
query_room_items(&st, &conn, ruuid, pagination)?
|
query_room_items(&st, &conn, rid, pagination)?
|
||||||
};
|
};
|
||||||
|
|
||||||
let items = items
|
let items = items
|
||||||
|
@ -506,7 +502,7 @@ async fn room_get_feed(
|
||||||
.config
|
.config
|
||||||
.server
|
.server
|
||||||
.base_url
|
.base_url
|
||||||
.join(&format!("/room/{ruuid}/feed.json"))
|
.join(&format!("/room/{rid}/feed.json"))
|
||||||
.expect("base_url must be valid");
|
.expect("base_url must be valid");
|
||||||
let next_url = skip_token.map(|skip_token| {
|
let next_url = skip_token.map(|skip_token| {
|
||||||
let next_params = Pagination {
|
let next_params = Pagination {
|
||||||
|
@ -573,7 +569,7 @@ struct FeedItemExtra {
|
||||||
|
|
||||||
#[derive(Debug, Serialize)]
|
#[derive(Debug, Serialize)]
|
||||||
pub struct RoomMetadata {
|
pub struct RoomMetadata {
|
||||||
pub ruuid: Uuid,
|
pub rid: Id,
|
||||||
pub title: String,
|
pub title: String,
|
||||||
pub attrs: RoomAttrs,
|
pub attrs: RoomAttrs,
|
||||||
|
|
||||||
|
@ -584,15 +580,15 @@ pub struct RoomMetadata {
|
||||||
|
|
||||||
fn get_room_if_readable<T>(
|
fn get_room_if_readable<T>(
|
||||||
conn: &rusqlite::Connection,
|
conn: &rusqlite::Connection,
|
||||||
ruuid: Uuid,
|
rid: Id,
|
||||||
user: Option<&UserKey>,
|
user: Option<&UserKey>,
|
||||||
f: impl FnOnce(&Row<'_>) -> rusqlite::Result<T>,
|
f: impl FnOnce(&Row<'_>) -> rusqlite::Result<T>,
|
||||||
) -> Result<T, ApiError> {
|
) -> Result<T, ApiError> {
|
||||||
conn.query_row(
|
conn.query_row(
|
||||||
r"
|
r"
|
||||||
SELECT `rid`, `title`, `attrs`
|
SELECT `title`, `attrs`
|
||||||
FROM `room`
|
FROM `room`
|
||||||
WHERE `ruuid` = :ruuid AND
|
WHERE `rid` = :rid AND
|
||||||
((`attrs` & :perm) = :perm OR
|
((`attrs` & :perm) = :perm OR
|
||||||
EXISTS(SELECT 1
|
EXISTS(SELECT 1
|
||||||
FROM `room_member`
|
FROM `room_member`
|
||||||
|
@ -601,8 +597,8 @@ fn get_room_if_readable<T>(
|
||||||
`userkey` = :userkey))
|
`userkey` = :userkey))
|
||||||
",
|
",
|
||||||
named_params! {
|
named_params! {
|
||||||
|
":rid": rid,
|
||||||
":perm": RoomAttrs::PUBLIC_READABLE,
|
":perm": RoomAttrs::PUBLIC_READABLE,
|
||||||
":ruuid": ruuid,
|
|
||||||
":userkey": user,
|
":userkey": user,
|
||||||
},
|
},
|
||||||
f,
|
f,
|
||||||
|
@ -618,17 +614,16 @@ type ChatItemWithId = (u64, ChatItem);
|
||||||
fn query_room_items(
|
fn query_room_items(
|
||||||
st: &AppState,
|
st: &AppState,
|
||||||
conn: &Connection,
|
conn: &Connection,
|
||||||
ruuid: Uuid,
|
rid: Id,
|
||||||
pagination: Pagination,
|
pagination: Pagination,
|
||||||
) -> Result<(Vec<ChatItemWithId>, Option<u64>), ApiError> {
|
) -> Result<(Vec<ChatItemWithId>, Option<u64>), ApiError> {
|
||||||
let page_len = pagination.effective_page_len(st);
|
let page_len = pagination.effective_page_len(st);
|
||||||
let mut stmt = conn.prepare(
|
let mut stmt = conn.prepare(
|
||||||
r"
|
r"
|
||||||
SELECT `cid`, `timestamp`, `nonce`, `sig`, `userkey`, `sig`, `rich_text`
|
SELECT `cid`, `timestamp`, `nonce`, `sig`, `userkey`, `sig`, `rich_text`
|
||||||
FROM `room`
|
FROM `room_item`
|
||||||
JOIN `room_item` USING (`rid`)
|
|
||||||
JOIN `user` USING (`uid`)
|
JOIN `user` USING (`uid`)
|
||||||
WHERE `ruuid` = :ruuid AND
|
WHERE `rid` = :rid AND
|
||||||
(:before_cid IS NULL OR `cid` < :before_cid)
|
(:before_cid IS NULL OR `cid` < :before_cid)
|
||||||
ORDER BY `cid` DESC
|
ORDER BY `cid` DESC
|
||||||
LIMIT :limit
|
LIMIT :limit
|
||||||
|
@ -637,7 +632,7 @@ fn query_room_items(
|
||||||
let items = stmt
|
let items = stmt
|
||||||
.query_and_then(
|
.query_and_then(
|
||||||
named_params! {
|
named_params! {
|
||||||
":ruuid": ruuid,
|
":rid": rid,
|
||||||
":before_cid": pagination.skip_token,
|
":before_cid": pagination.skip_token,
|
||||||
":limit": page_len,
|
":limit": page_len,
|
||||||
},
|
},
|
||||||
|
@ -650,7 +645,7 @@ fn query_room_items(
|
||||||
timestamp: row.get("timestamp")?,
|
timestamp: row.get("timestamp")?,
|
||||||
user: row.get("userkey")?,
|
user: row.get("userkey")?,
|
||||||
payload: ChatPayload {
|
payload: ChatPayload {
|
||||||
room: ruuid,
|
room: rid,
|
||||||
rich_text: row.get("rich_text")?,
|
rich_text: row.get("rich_text")?,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -667,10 +662,10 @@ fn query_room_items(
|
||||||
|
|
||||||
async fn room_post_item(
|
async fn room_post_item(
|
||||||
st: ArcState,
|
st: ArcState,
|
||||||
R(Path(ruuid), _): RE<Path<Uuid>>,
|
R(Path(rid), _): RE<Path<Id>>,
|
||||||
SignedJson(chat): SignedJson<ChatPayload>,
|
SignedJson(chat): SignedJson<ChatPayload>,
|
||||||
) -> Result<Json<u64>, ApiError> {
|
) -> Result<Json<u64>, ApiError> {
|
||||||
if ruuid != chat.signee.payload.room {
|
if rid != chat.signee.payload.room {
|
||||||
return Err(error_response!(
|
return Err(error_response!(
|
||||||
StatusCode::BAD_REQUEST,
|
StatusCode::BAD_REQUEST,
|
||||||
"invalid_request",
|
"invalid_request",
|
||||||
|
@ -680,25 +675,28 @@ async fn room_post_item(
|
||||||
|
|
||||||
let (cid, txs) = {
|
let (cid, txs) = {
|
||||||
let conn = st.db.get();
|
let conn = st.db.get();
|
||||||
let Some((rid, uid)) = conn
|
let Some((uid, _perm)) = conn
|
||||||
.query_row(
|
.query_row(
|
||||||
r"
|
r"
|
||||||
SELECT `rid`, `uid`
|
SELECT `uid`, `room_member`.`permission`
|
||||||
FROM `room`
|
FROM `room_member`
|
||||||
JOIN `room_member` USING (`rid`)
|
|
||||||
JOIN `user` USING (`uid`)
|
JOIN `user` USING (`uid`)
|
||||||
WHERE `ruuid` = :ruuid AND
|
WHERE `rid` = :rid AND
|
||||||
`userkey` = :userkey AND
|
`userkey` = :userkey
|
||||||
(`room_member`.`permission` & :perm) = :perm
|
|
||||||
",
|
",
|
||||||
named_params! {
|
named_params! {
|
||||||
":ruuid": ruuid,
|
":rid": rid,
|
||||||
":userkey": &chat.signee.user,
|
":userkey": &chat.signee.user,
|
||||||
":perm": MemberPermission::POST_CHAT,
|
|
||||||
},
|
},
|
||||||
|row| Ok((row.get::<_, u64>("rid")?, row.get::<_, u64>("uid")?)),
|
|row| {
|
||||||
|
Ok((
|
||||||
|
row.get::<_, u64>("uid")?,
|
||||||
|
row.get::<_, MemberPermission>("permission")?,
|
||||||
|
))
|
||||||
|
},
|
||||||
)
|
)
|
||||||
.optional()?
|
.optional()?
|
||||||
|
.filter(|(_, perm)| perm.contains(MemberPermission::POST_CHAT))
|
||||||
else {
|
else {
|
||||||
return Err(error_response!(
|
return Err(error_response!(
|
||||||
StatusCode::FORBIDDEN,
|
StatusCode::FORBIDDEN,
|
||||||
|
@ -759,10 +757,10 @@ async fn room_post_item(
|
||||||
|
|
||||||
async fn room_admin(
|
async fn room_admin(
|
||||||
st: ArcState,
|
st: ArcState,
|
||||||
R(Path(ruuid), _): RE<Path<Uuid>>,
|
R(Path(rid), _): RE<Path<Id>>,
|
||||||
SignedJson(op): SignedJson<RoomAdminPayload>,
|
SignedJson(op): SignedJson<RoomAdminPayload>,
|
||||||
) -> Result<StatusCode, ApiError> {
|
) -> Result<StatusCode, ApiError> {
|
||||||
if ruuid != op.signee.payload.room {
|
if rid != op.signee.payload.room {
|
||||||
return Err(error_response!(
|
return Err(error_response!(
|
||||||
StatusCode::BAD_REQUEST,
|
StatusCode::BAD_REQUEST,
|
||||||
"invalid_request",
|
"invalid_request",
|
||||||
|
@ -786,7 +784,7 @@ async fn room_admin(
|
||||||
"invalid permission",
|
"invalid permission",
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
room_join(&st, ruuid, user, permission).await?;
|
room_join(&st, rid, user, permission).await?;
|
||||||
}
|
}
|
||||||
RoomAdminOp::RemoveMember { user } => {
|
RoomAdminOp::RemoveMember { user } => {
|
||||||
if user != op.signee.user {
|
if user != op.signee.user {
|
||||||
|
@ -796,7 +794,7 @@ async fn room_admin(
|
||||||
"only self-removing is implemented yet",
|
"only self-removing is implemented yet",
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
room_leave(&st, ruuid, user).await?;
|
room_leave(&st, rid, user).await?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -805,34 +803,32 @@ async fn room_admin(
|
||||||
|
|
||||||
async fn room_join(
|
async fn room_join(
|
||||||
st: &AppState,
|
st: &AppState,
|
||||||
ruuid: Uuid,
|
rid: Id,
|
||||||
user: UserKey,
|
user: UserKey,
|
||||||
permission: MemberPermission,
|
permission: MemberPermission,
|
||||||
) -> Result<(), ApiError> {
|
) -> Result<(), ApiError> {
|
||||||
let mut conn = st.db.get();
|
let mut conn = st.db.get();
|
||||||
let txn = conn.transaction()?;
|
let txn = conn.transaction()?;
|
||||||
let Some(rid) = txn
|
let is_public_joinable = txn
|
||||||
.query_row(
|
.query_row(
|
||||||
r"
|
r"
|
||||||
SELECT `rid`
|
SELECT `attrs`
|
||||||
FROM `room`
|
FROM `room`
|
||||||
WHERE `ruuid` = :ruuid AND
|
WHERE `rid` = ?
|
||||||
(`room`.`attrs` & :joinable) = :joinable
|
|
||||||
",
|
",
|
||||||
named_params! {
|
params![rid],
|
||||||
":ruuid": ruuid,
|
|row| row.get::<_, RoomAttrs>(0),
|
||||||
":joinable": RoomAttrs::PUBLIC_JOINABLE,
|
|
||||||
},
|
|
||||||
|row| row.get::<_, u64>("rid"),
|
|
||||||
)
|
)
|
||||||
.optional()?
|
.optional()?
|
||||||
else {
|
.is_some_and(|attrs| attrs.contains(RoomAttrs::PUBLIC_JOINABLE));
|
||||||
|
if !is_public_joinable {
|
||||||
return Err(error_response!(
|
return Err(error_response!(
|
||||||
StatusCode::FORBIDDEN,
|
StatusCode::FORBIDDEN,
|
||||||
"permission_denied",
|
"permission_denied",
|
||||||
"room does not exists or user is not allowed to join this room",
|
"room does not exists or user is not allowed to join this room",
|
||||||
));
|
));
|
||||||
};
|
}
|
||||||
|
|
||||||
txn.execute(
|
txn.execute(
|
||||||
r"
|
r"
|
||||||
INSERT INTO `user` (`userkey`)
|
INSERT INTO `user` (`userkey`)
|
||||||
|
@ -860,25 +856,24 @@ async fn room_join(
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn room_leave(st: &AppState, ruuid: Uuid, user: UserKey) -> Result<(), ApiError> {
|
async fn room_leave(st: &AppState, rid: Id, user: UserKey) -> Result<(), ApiError> {
|
||||||
let mut conn = st.db.get();
|
let mut conn = st.db.get();
|
||||||
let txn = conn.transaction()?;
|
let txn = conn.transaction()?;
|
||||||
|
|
||||||
let Some((rid, uid)) = txn
|
let Some(uid) = txn
|
||||||
.query_row(
|
.query_row(
|
||||||
r"
|
r"
|
||||||
SELECT `rid`, `uid`
|
SELECT `uid`
|
||||||
FROM `room_member`
|
FROM `room_member`
|
||||||
JOIN `room` USING (`rid`)
|
|
||||||
JOIN `user` USING (`uid`)
|
JOIN `user` USING (`uid`)
|
||||||
WHERE `ruuid` = :ruuid AND
|
WHERE `rid` = :rid AND
|
||||||
`userkey` = :userkey
|
`userkey` = :userkey
|
||||||
",
|
",
|
||||||
named_params! {
|
named_params! {
|
||||||
":ruuid": ruuid,
|
":rid": rid,
|
||||||
":userkey": user,
|
":userkey": user,
|
||||||
},
|
},
|
||||||
|row| Ok((row.get::<_, u64>("rid")?, row.get::<_, u64>("uid")?)),
|
|row| row.get::<_, u64>("uid"),
|
||||||
)
|
)
|
||||||
.optional()?
|
.optional()?
|
||||||
else {
|
else {
|
||||||
|
|
|
@ -168,13 +168,13 @@ async function genAuthHeader() {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async function enterRoom(ruuid) {
|
async function enterRoom(rid) {
|
||||||
log(`loading room: ${ruuid}`);
|
log(`loading room: ${rid}`);
|
||||||
curRoom = ruuid;
|
curRoom = rid;
|
||||||
roomsInput.value = ruuid;
|
roomsInput.value = rid;
|
||||||
|
|
||||||
genAuthHeader()
|
genAuthHeader()
|
||||||
.then(opts => fetch(`${serverUrl}/room/${ruuid}`, opts))
|
.then(opts => fetch(`${serverUrl}/room/${rid}`, opts))
|
||||||
.then(async (resp) => [resp.status, await resp.json()])
|
.then(async (resp) => [resp.status, await resp.json()])
|
||||||
.then(async ([status, json]) => {
|
.then(async ([status, json]) => {
|
||||||
if (status !== 200) throw new Error(`status ${status}: ${json.error.message}`);
|
if (status !== 200) throw new Error(`status ${status}: ${json.error.message}`);
|
||||||
|
@ -185,7 +185,7 @@ async function enterRoom(ruuid) {
|
||||||
});
|
});
|
||||||
|
|
||||||
genAuthHeader()
|
genAuthHeader()
|
||||||
.then(opts => fetch(`${serverUrl}/room/${ruuid}/item`, opts))
|
.then(opts => fetch(`${serverUrl}/room/${rid}/item`, opts))
|
||||||
.then(async (resp) => { return [resp.status, await resp.json()]; })
|
.then(async (resp) => { return [resp.status, await resp.json()]; })
|
||||||
.then(async ([status, json]) => {
|
.then(async ([status, json]) => {
|
||||||
if (status !== 200) throw new Error(`status ${status}: ${json.error.message}`);
|
if (status !== 200) throw new Error(`status ${status}: ${json.error.message}`);
|
||||||
|
@ -267,10 +267,10 @@ async function loadRoomList(autoJoin) {
|
||||||
const resp = await fetch(`${serverUrl}/room?filter=${filter}`, await genAuthHeader())
|
const resp = await fetch(`${serverUrl}/room?filter=${filter}`, await genAuthHeader())
|
||||||
const json = await resp.json()
|
const json = await resp.json()
|
||||||
if (resp.status !== 200) throw new Error(`status ${resp.status}: ${json.error.message}`);
|
if (resp.status !== 200) throw new Error(`status ${resp.status}: ${json.error.message}`);
|
||||||
for (const { ruuid, title, attrs } of json.rooms) {
|
for (const { rid, title, attrs } of json.rooms) {
|
||||||
const el = document.createElement('option');
|
const el = document.createElement('option');
|
||||||
el.value = ruuid;
|
el.value = rid;
|
||||||
el.innerText = `${title} (uuid=${ruuid}, attrs=${attrs})`;
|
el.innerText = `${title} (rid=${rid}, attrs=${attrs})`;
|
||||||
targetEl.appendChild(el);
|
targetEl.appendChild(el);
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
@ -291,19 +291,19 @@ async function loadRoomList(autoJoin) {
|
||||||
loadInto(joinNewRoomInput, 'public')
|
loadInto(joinNewRoomInput, 'public')
|
||||||
}
|
}
|
||||||
|
|
||||||
async function joinRoom(ruuid) {
|
async function joinRoom(rid) {
|
||||||
try {
|
try {
|
||||||
joinNewRoomInput.disabled = true;
|
joinNewRoomInput.disabled = true;
|
||||||
await signAndPost(`${serverUrl}/room/${ruuid}/admin`, {
|
await signAndPost(`${serverUrl}/room/${rid}/admin`, {
|
||||||
// sorted fields.
|
// sorted fields.
|
||||||
permission: 1, // POST_CHAT
|
permission: 1, // POST_CHAT
|
||||||
room: ruuid,
|
room: rid,
|
||||||
typ: 'add_member',
|
typ: 'add_member',
|
||||||
user: await getUserPubkey(),
|
user: await getUserPubkey(),
|
||||||
});
|
});
|
||||||
log('joined room');
|
log('joined room');
|
||||||
await loadRoomList(false)
|
await loadRoomList(false)
|
||||||
await enterRoom(ruuid);
|
await enterRoom(rid);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
log(`failed to join room: ${e}`);
|
log(`failed to join room: ${e}`);
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
// Re-export of public dependencies.
|
// Re-export of public dependencies.
|
||||||
pub use bitflags;
|
pub use bitflags;
|
||||||
pub use ed25519_dalek;
|
pub use ed25519_dalek;
|
||||||
pub use uuid;
|
|
||||||
|
|
||||||
pub mod types;
|
pub mod types;
|
||||||
|
|
58
src/types.rs
58
src/types.rs
|
@ -9,7 +9,20 @@ use ed25519_dalek::{
|
||||||
};
|
};
|
||||||
use rand_core::RngCore;
|
use rand_core::RngCore;
|
||||||
use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
|
use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
|
||||||
use uuid::Uuid;
|
use serde_with::{serde_as, DisplayFromStr};
|
||||||
|
|
||||||
|
/// An opaque server-specific ID for room, chat item, and etc.
|
||||||
|
/// It's currently serialized as a string for JavaScript's convenience.
|
||||||
|
#[serde_as]
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
|
||||||
|
#[serde(transparent)]
|
||||||
|
pub struct Id(#[serde_as(as = "DisplayFromStr")] pub i64);
|
||||||
|
|
||||||
|
impl fmt::Display for Id {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
self.0.fmt(f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
#[serde(transparent)]
|
#[serde(transparent)]
|
||||||
|
@ -82,7 +95,7 @@ impl<T: Serialize> WithSig<T> {
|
||||||
#[serde(tag = "typ", rename = "chat")]
|
#[serde(tag = "typ", rename = "chat")]
|
||||||
pub struct ChatPayload {
|
pub struct ChatPayload {
|
||||||
pub rich_text: RichText,
|
pub rich_text: RichText,
|
||||||
pub room: Uuid,
|
pub room: Id,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Ref: <https://github.com/Blah-IM/Weblah/blob/a3fa0f265af54c846f8d65f42aa4409c8dba9dd9/src/lib/richText.ts>
|
/// Ref: <https://github.com/Blah-IM/Weblah/blob/a3fa0f265af54c846f8d65f42aa4409c8dba9dd9/src/lib/richText.ts>
|
||||||
|
@ -329,7 +342,7 @@ pub struct AuthPayload {}
|
||||||
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
|
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
#[serde(tag = "typ", rename_all = "snake_case")]
|
#[serde(tag = "typ", rename_all = "snake_case")]
|
||||||
pub struct RoomAdminPayload {
|
pub struct RoomAdminPayload {
|
||||||
pub room: Uuid,
|
pub room: Id,
|
||||||
#[serde(flatten)]
|
#[serde(flatten)]
|
||||||
pub op: RoomAdminOp,
|
pub op: RoomAdminOp,
|
||||||
}
|
}
|
||||||
|
@ -385,6 +398,18 @@ mod sql_impl {
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
|
impl ToSql for Id {
|
||||||
|
fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
|
||||||
|
self.0.to_sql()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromSql for Id {
|
||||||
|
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
|
||||||
|
i64::column_result(value).map(Self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl ToSql for UserKey {
|
impl ToSql for UserKey {
|
||||||
fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
|
fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
|
||||||
// TODO: Extensive key format?
|
// TODO: Extensive key format?
|
||||||
|
@ -441,6 +466,8 @@ mod sql_impl {
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
use expect_test::expect;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -454,16 +481,17 @@ mod tests {
|
||||||
&mut fake_rng,
|
&mut fake_rng,
|
||||||
ChatPayload {
|
ChatPayload {
|
||||||
rich_text: RichText::from("hello"),
|
rich_text: RichText::from("hello"),
|
||||||
room: Uuid::nil(),
|
room: Id(42),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let json = serde_jcs::to_string(&item).unwrap();
|
let json = serde_jcs::to_string(&item).unwrap();
|
||||||
assert_eq!(
|
let expect = expect![[
|
||||||
json,
|
r#"{"sig":"18ee190722bebfd438c82f34890540d91578b4ba9f6c0c6011cc4fd751a321e32e9442d00dad1920799c54db011694c72a9ba993b408922e9997119209aa5e09","signee":{"nonce":66,"payload":{"rich_text":["hello"],"room":"42","typ":"chat"},"timestamp":3735928559,"user":"2152f8d19b791d24453242e15f2eab6cb7cffa7b6a5ed30097960e069881db12"}}"#
|
||||||
r#"{"sig":"5e52985dc9e43a77267f0b383a8223af96f36e83c180a36da627dfac6504b2bb4c6b80c9903a6c3a0bbc742718466d72af4407a8e74d41af5cb0137cf3798d08","signee":{"nonce":66,"payload":{"rich_text":["hello"],"room":"00000000-0000-0000-0000-000000000000","typ":"chat"},"timestamp":3735928559,"user":"2152f8d19b791d24453242e15f2eab6cb7cffa7b6a5ed30097960e069881db12"}}"#
|
]];
|
||||||
);
|
expect.assert_eq(&json);
|
||||||
|
|
||||||
let roundtrip_item = serde_json::from_str::<WithSig<ChatPayload>>(&json).unwrap();
|
let roundtrip_item = serde_json::from_str::<WithSig<ChatPayload>>(&json).unwrap();
|
||||||
// assert_eq!(roundtrip_item, item);
|
// assert_eq!(roundtrip_item, item);
|
||||||
roundtrip_item.verify().unwrap();
|
roundtrip_item.verify().unwrap();
|
||||||
|
@ -503,7 +531,7 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn room_admin_serde() {
|
fn room_admin_serde() {
|
||||||
let data = RoomAdminPayload {
|
let data = RoomAdminPayload {
|
||||||
room: Uuid::nil(),
|
room: Id(42),
|
||||||
op: RoomAdminOp::AddMember {
|
op: RoomAdminOp::AddMember {
|
||||||
permission: MemberPermission::POST_CHAT,
|
permission: MemberPermission::POST_CHAT,
|
||||||
user: UserKey([0x42; PUBLIC_KEY_LENGTH]),
|
user: UserKey([0x42; PUBLIC_KEY_LENGTH]),
|
||||||
|
@ -511,11 +539,11 @@ mod tests {
|
||||||
};
|
};
|
||||||
let raw = serde_jcs::to_string(&data).unwrap();
|
let raw = serde_jcs::to_string(&data).unwrap();
|
||||||
|
|
||||||
assert_eq!(
|
let expect = expect![[
|
||||||
raw,
|
r#"{"permission":1,"room":"42","typ":"add_member","user":"4242424242424242424242424242424242424242424242424242424242424242"}"#
|
||||||
r#"{"permission":1,"room":"00000000-0000-0000-0000-000000000000","typ":"add_member","user":"4242424242424242424242424242424242424242424242424242424242424242"}"#
|
]];
|
||||||
);
|
expect.assert_eq(&raw);
|
||||||
let got = serde_json::from_str::<RoomAdminPayload>(&raw).unwrap();
|
let roundtrip = serde_json::from_str::<RoomAdminPayload>(&raw).unwrap();
|
||||||
assert_eq!(got, data);
|
assert_eq!(roundtrip, data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue