Constellation, Spacedust, Slingshot, UFOs: atproto crates and services for microcosm

Merge pull request #16 from at-microcosm/constellation/filtering

Filter links by linker dids

Changed files
+790 -417
constellation
reflector
src
spacedust
who-am-i
src
+163 -133
Cargo.lock
···
"proc-macro2",
"quote",
"serde",
-
"syn 2.0.103",
+
"syn 2.0.106",
]
[[package]]
···
dependencies = [
"proc-macro2",
"quote",
-
"syn 2.0.103",
+
"syn 2.0.106",
"synstructure",
]
···
dependencies = [
"proc-macro2",
"quote",
-
"syn 2.0.103",
+
"syn 2.0.106",
]
[[package]]
···
dependencies = [
"proc-macro2",
"quote",
-
"syn 2.0.103",
+
"syn 2.0.106",
]
[[package]]
···
dependencies = [
"proc-macro2",
"quote",
-
"syn 2.0.103",
+
"syn 2.0.106",
]
[[package]]
···
"derive_utils",
"proc-macro2",
"quote",
-
"syn 2.0.103",
+
"syn 2.0.106",
]
[[package]]
···
"axum-core",
"bytes",
"cookie",
+
"form_urlencoded",
"futures-util",
"headers",
"http",
···
"pin-project-lite",
"rustversion",
"serde",
+
"serde_html_form",
+
"serde_path_to_error",
"tower",
"tower-layer",
"tower-service",
···
"regex",
"rustc-hash 1.1.0",
"shlex",
-
"syn 2.0.103",
+
"syn 2.0.106",
"which",
]
···
"regex",
"rustc-hash 1.1.0",
"shlex",
-
"syn 2.0.103",
+
"syn 2.0.106",
]
[[package]]
···
"regex",
"rustc-hash 2.1.1",
"shlex",
-
"syn 2.0.103",
+
"syn 2.0.106",
]
[[package]]
···
[[package]]
name = "camino"
-
version = "1.1.9"
+
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-
checksum = "8b96ec4966b5813e2c0507c1f86115c8c5abaadc3980879c3424042a02fd1ad3"
+
checksum = "276a59bf2b2c967788139340c9f0c5b12d7fd6630315c15c217e559de85d2609"
dependencies = [
-
"serde",
+
"serde_core",
]
[[package]]
···
"heck",
"proc-macro2",
"quote",
-
"syn 2.0.103",
+
"syn 2.0.106",
[[package]]
···
"proc-macro2",
"quote",
"strsim 0.11.1",
-
"syn 2.0.103",
+
"syn 2.0.106",
[[package]]
···
dependencies = [
"darling_core 0.20.11",
"quote",
-
"syn 2.0.103",
+
"syn 2.0.106",
[[package]]
···
checksum = "18e4fdb82bd54a12e42fb58a800dcae6b9e13982238ce2296dc3570b92148e1f"
dependencies = [
"data-encoding",
-
"syn 2.0.103",
+
"syn 1.0.109",
[[package]]
···
"darling 0.20.11",
"proc-macro2",
"quote",
-
"syn 2.0.103",
+
"syn 2.0.106",
[[package]]
···
checksum = "ab63b0e2bf4d5928aff72e83a7dace85d7bba5fe12dcc3c5a572d78caffd3f3c"
dependencies = [
"derive_builder_core",
-
"syn 2.0.103",
+
"syn 2.0.106",
[[package]]
···
dependencies = [
"proc-macro2",
"quote",
-
"syn 2.0.103",
+
"syn 2.0.106",
"unicode-xid",
···
dependencies = [
"proc-macro2",
"quote",
-
"syn 2.0.103",
+
"syn 2.0.106",
[[package]]
···
dependencies = [
"proc-macro2",
"quote",
-
"syn 2.0.103",
+
"syn 2.0.106",
[[package]]
···
[[package]]
name = "dropshot"
-
version = "0.16.2"
+
version = "0.16.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-
checksum = "50e8fed669e35e757646ad10f97c4d26dd22cce3da689b307954f7000d2719d0"
+
checksum = "eedf902e40c1024b8ed9ca16378a54e9655cdf0e698245ba82d81a3778dcbc54"
dependencies = [
"async-stream",
"async-trait",
···
"http-body-util",
"hyper",
"hyper-util",
-
"indexmap 2.9.0",
+
"indexmap 2.11.4",
"multer",
"openapiv3",
"paste",
···
"thiserror 2.0.16",
"tokio",
"tokio-rustls 0.25.0",
-
"toml",
+
"toml 0.9.7",
"uuid",
"version_check",
"waitgroup",
···
[[package]]
name = "dropshot_endpoint"
-
version = "0.16.2"
+
version = "0.16.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-
checksum = "acebb687581abdeaa2c89fa448818a5f803b0e68e5d7e7a1cf585a8f3c5c57ac"
+
checksum = "89d09440e73a9dcf8a0f7fbd6ab889a7751d59f0fe76e5082a0a6d5623ec6da3"
dependencies = [
"heck",
"proc-macro2",
···
"semver",
"serde",
"serde_tokenstream",
-
"syn 2.0.103",
+
"syn 2.0.106",
[[package]]
···
"heck",
"proc-macro2",
"quote",
-
"syn 2.0.103",
+
"syn 2.0.106",
[[package]]
···
"once_cell",
"proc-macro2",
"quote",
-
"syn 2.0.103",
+
"syn 2.0.106",
[[package]]
···
dependencies = [
"proc-macro2",
"quote",
-
"syn 2.0.103",
+
"syn 2.0.106",
[[package]]
···
"futures-core",
"futures-sink",
"http",
-
"indexmap 2.9.0",
+
"indexmap 2.11.4",
"slab",
"tokio",
"tokio-util",
···
"js-sys",
"log",
"wasm-bindgen",
-
"windows-core 0.61.0",
+
"windows-core",
[[package]]
···
dependencies = [
"proc-macro2",
"quote",
-
"syn 2.0.103",
+
"syn 2.0.106",
[[package]]
···
[[package]]
name = "indexmap"
-
version = "2.9.0"
+
version = "2.11.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-
checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e"
+
checksum = "4b0f83760fb341a774ed326568e19f5a863af4a952def8c39f9ab92fd95b88e5"
dependencies = [
"equivalent",
"hashbrown 0.15.2",
"serde",
+
"serde_core",
[[package]]
···
dependencies = [
"proc-macro2",
"quote",
-
"syn 2.0.103",
+
"syn 2.0.106",
[[package]]
···
checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34"
dependencies = [
"cfg-if",
-
"windows-targets 0.52.6",
+
"windows-targets 0.48.5",
[[package]]
···
"spin",
"tokio",
"tokio-util",
-
"toml",
+
"toml 0.8.23",
"tracing",
"tracing-subscriber",
···
"http-body-util",
"hyper",
"hyper-util",
-
"indexmap 2.9.0",
+
"indexmap 2.11.4",
"ipnet",
"metrics",
"metrics-util 0.19.0",
···
"hyper",
"hyper-rustls",
"hyper-util",
-
"indexmap 2.9.0",
+
"indexmap 2.11.4",
"ipnet",
"metrics",
"metrics-util 0.20.0",
···
[[package]]
name = "openapiv3"
-
version = "2.0.0"
+
version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-
checksum = "cc02deea53ffe807708244e5914f6b099ad7015a207ee24317c22112e17d9c5c"
+
checksum = "5c8d427828b22ae1fff2833a03d8486c2c881367f1c336349f307f321e7f4d05"
dependencies = [
-
"indexmap 2.9.0",
+
"indexmap 2.11.4",
"serde",
"serde_json",
···
dependencies = [
"proc-macro2",
"quote",
-
"syn 2.0.103",
+
"syn 2.0.106",
[[package]]
···
"pest_meta",
"proc-macro2",
"quote",
-
"syn 2.0.103",
+
"syn 2.0.106",
[[package]]
···
dependencies = [
"proc-macro2",
"quote",
-
"syn 2.0.103",
+
"syn 2.0.106",
[[package]]
···
"proc-macro-crate",
"proc-macro2",
"quote",
-
"syn 2.0.103",
+
"syn 2.0.106",
[[package]]
···
"bytes",
"derive_more",
"futures-util",
-
"indexmap 2.9.0",
+
"indexmap 2.11.4",
"itertools 0.14.0",
"mime",
"num-traits",
···
dependencies = [
"darling 0.20.11",
"http",
-
"indexmap 2.9.0",
+
"indexmap 2.11.4",
"mime",
"proc-macro-crate",
"proc-macro2",
"quote",
"regex",
-
"syn 2.0.103",
+
"syn 2.0.106",
"thiserror 2.0.16",
···
checksum = "6837b9e10d61f45f987d50808f83d1ee3d206c66acf650c3e4ae2e1f6ddedf55"
dependencies = [
"proc-macro2",
-
"syn 2.0.103",
+
"syn 2.0.106",
[[package]]
···
dependencies = [
"proc-macro2",
"quote",
-
"syn 2.0.103",
+
"syn 2.0.106",
[[package]]
···
"proc-macro2",
"quote",
"serde_derive_internals",
-
"syn 2.0.103",
+
"syn 2.0.106",
[[package]]
···
[[package]]
name = "serde"
-
version = "1.0.219"
+
version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
-
checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6"
+
checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
dependencies = [
+
"serde_core",
"serde_derive",
···
[[package]]
+
name = "serde_core"
+
version = "1.0.228"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad"
+
dependencies = [
+
"serde_derive",
+
]
+
+
[[package]]
name = "serde_derive"
-
version = "1.0.219"
+
version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
-
checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00"
+
checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
dependencies = [
"proc-macro2",
"quote",
-
"syn 2.0.103",
+
"syn 2.0.106",
[[package]]
···
dependencies = [
"proc-macro2",
"quote",
-
"syn 2.0.103",
+
"syn 2.0.106",
[[package]]
···
checksum = "9d2de91cf02bbc07cde38891769ccd5d4f073d22a40683aa4bc7a95781aaa2c4"
dependencies = [
"form_urlencoded",
-
"indexmap 2.9.0",
+
"indexmap 2.11.4",
"itoa",
"ryu",
"serde",
···
[[package]]
name = "serde_json"
-
version = "1.0.141"
+
version = "1.0.145"
source = "registry+https://github.com/rust-lang/crates.io-index"
-
checksum = "30b9eff21ebe718216c6ec64e1d9ac57087aad11efc64e32002bce4a0d4c03d3"
+
checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c"
dependencies = [
"itoa",
"memchr",
"ryu",
"serde",
+
"serde_core",
[[package]]
···
[[package]]
+
name = "serde_spanned"
+
version = "1.0.2"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "5417783452c2be558477e104686f7de5dae53dba813c28435e0e70f82d9b04ee"
+
dependencies = [
+
"serde_core",
+
]
+
+
[[package]]
name = "serde_tokenstream"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
···
"proc-macro2",
"quote",
"serde",
-
"syn 2.0.103",
+
"syn 2.0.106",
[[package]]
···
"chrono",
"hex",
"indexmap 1.9.3",
-
"indexmap 2.9.0",
+
"indexmap 2.11.4",
"serde",
"serde_derive",
"serde_json",
···
"darling 0.20.11",
"proc-macro2",
"quote",
-
"syn 2.0.103",
+
"syn 2.0.106",
[[package]]
···
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47"
dependencies = [
-
"indexmap 2.9.0",
+
"indexmap 2.11.4",
"itoa",
"ryu",
"serde",
···
[[package]]
name = "syn"
-
version = "2.0.103"
+
version = "2.0.106"
source = "registry+https://github.com/rust-lang/crates.io-index"
-
checksum = "e4307e30089d6fd6aff212f2da3a1f9e32f3223b1f010fb09b7c95f90f3ca1e8"
+
checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6"
dependencies = [
"proc-macro2",
"quote",
···
dependencies = [
"proc-macro2",
"quote",
-
"syn 2.0.103",
+
"syn 2.0.106",
[[package]]
···
dependencies = [
"proc-macro2",
"quote",
-
"syn 2.0.103",
+
"syn 2.0.106",
[[package]]
···
dependencies = [
"proc-macro2",
"quote",
-
"syn 2.0.103",
+
"syn 2.0.106",
[[package]]
···
dependencies = [
"proc-macro2",
"quote",
-
"syn 2.0.103",
+
"syn 2.0.106",
[[package]]
···
checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362"
dependencies = [
"serde",
-
"serde_spanned",
-
"toml_datetime",
+
"serde_spanned 0.6.9",
+
"toml_datetime 0.6.11",
"toml_edit",
[[package]]
+
name = "toml"
+
version = "0.9.7"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "00e5e5d9bf2475ac9d4f0d9edab68cc573dc2fd644b0dba36b0c30a92dd9eaa0"
+
dependencies = [
+
"indexmap 2.11.4",
+
"serde_core",
+
"serde_spanned 1.0.2",
+
"toml_datetime 0.7.2",
+
"toml_parser",
+
"toml_writer",
+
"winnow",
+
]
+
+
[[package]]
name = "toml_datetime"
version = "0.6.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
···
[[package]]
+
name = "toml_datetime"
+
version = "0.7.2"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "32f1085dec27c2b6632b04c80b3bb1b4300d6495d1e129693bdda7d91e72eec1"
+
dependencies = [
+
"serde_core",
+
]
+
+
[[package]]
name = "toml_edit"
version = "0.22.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a"
dependencies = [
-
"indexmap 2.9.0",
+
"indexmap 2.11.4",
"serde",
-
"serde_spanned",
-
"toml_datetime",
+
"serde_spanned 0.6.9",
+
"toml_datetime 0.6.11",
"toml_write",
"winnow",
[[package]]
+
name = "toml_parser"
+
version = "1.0.3"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "4cf893c33be71572e0e9aa6dd15e6677937abd686b066eac3f8cd3531688a627"
+
dependencies = [
+
"winnow",
+
]
+
+
[[package]]
name = "toml_write"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801"
[[package]]
+
name = "toml_writer"
+
version = "1.0.3"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "d163a63c116ce562a22cda521fcc4d79152e7aba014456fb5eb442f6d6a10109"
+
+
[[package]]
name = "tower"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
···
dependencies = [
"proc-macro2",
"quote",
-
"syn 2.0.103",
+
"syn 2.0.106",
[[package]]
···
dependencies = [
"proc-macro2",
"quote",
-
"syn 2.0.103",
+
"syn 2.0.106",
[[package]]
···
[[package]]
name = "uuid"
-
version = "1.16.0"
+
version = "1.18.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-
checksum = "458f7a779bf54acc9f347480ac654f68407d3aab21269a6e3c9f922acd9e2da9"
+
checksum = "2f87b8aa10b915a06587d0dec516c282ff295b475d94abf425d62b57710070a2"
dependencies = [
"getrandom 0.3.3",
+
"js-sys",
"serde",
+
"wasm-bindgen",
[[package]]
···
"log",
"proc-macro2",
"quote",
-
"syn 2.0.103",
+
"syn 2.0.106",
"wasm-bindgen-shared",
···
dependencies = [
"proc-macro2",
"quote",
-
"syn 2.0.103",
+
"syn 2.0.106",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
···
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb"
dependencies = [
-
"windows-sys 0.59.0",
+
"windows-sys 0.48.0",
[[package]]
···
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd04d41d93c4992d421894c18c8b43496aa748dd4c081bac0dc93eb0489272b6"
dependencies = [
-
"windows-core 0.58.0",
+
"windows-core",
"windows-targets 0.52.6",
···
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ba6d44ec8c2591c134257ce647b7ea6b20335bf6379a27dac5f1641fcf59f99"
dependencies = [
-
"windows-implement 0.58.0",
-
"windows-interface 0.58.0",
+
"windows-implement",
+
"windows-interface",
"windows-result 0.2.0",
"windows-strings 0.1.0",
"windows-targets 0.52.6",
[[package]]
-
name = "windows-core"
-
version = "0.61.0"
-
source = "registry+https://github.com/rust-lang/crates.io-index"
-
checksum = "4763c1de310c86d75a878046489e2e5ba02c649d185f21c67d4cf8a56d098980"
-
dependencies = [
-
"windows-implement 0.60.0",
-
"windows-interface 0.59.1",
-
"windows-link",
-
"windows-result 0.3.4",
-
"windows-strings 0.4.2",
-
]
-
-
[[package]]
name = "windows-implement"
version = "0.58.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
···
dependencies = [
"proc-macro2",
"quote",
-
"syn 2.0.103",
-
]
-
-
[[package]]
-
name = "windows-implement"
-
version = "0.60.0"
-
source = "registry+https://github.com/rust-lang/crates.io-index"
-
checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836"
-
dependencies = [
-
"proc-macro2",
-
"quote",
-
"syn 2.0.103",
+
"syn 2.0.106",
[[package]]
···
dependencies = [
"proc-macro2",
"quote",
-
"syn 2.0.103",
-
]
-
-
[[package]]
-
name = "windows-interface"
-
version = "0.59.1"
-
source = "registry+https://github.com/rust-lang/crates.io-index"
-
checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8"
-
dependencies = [
-
"proc-macro2",
-
"quote",
-
"syn 2.0.103",
+
"syn 2.0.106",
[[package]]
···
[[package]]
name = "winnow"
-
version = "0.7.11"
+
version = "0.7.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
-
checksum = "74c7b26e3480b707944fc872477815d29a8e429d2f93a1ce000f5fa84a15cbcd"
+
checksum = "21a0236b59786fed61e2a80582dd500fe61f18b5dca67a4a067d0bc9039339cf"
dependencies = [
"memchr",
···
dependencies = [
"proc-macro2",
"quote",
-
"syn 2.0.103",
+
"syn 2.0.106",
"synstructure",
···
dependencies = [
"proc-macro2",
"quote",
-
"syn 2.0.103",
+
"syn 2.0.106",
[[package]]
···
dependencies = [
"proc-macro2",
"quote",
-
"syn 2.0.103",
+
"syn 2.0.106",
[[package]]
···
dependencies = [
"proc-macro2",
"quote",
-
"syn 2.0.103",
+
"syn 2.0.106",
"synstructure",
···
dependencies = [
"proc-macro2",
"quote",
-
"syn 2.0.103",
+
"syn 2.0.106",
[[package]]
···
dependencies = [
"proc-macro2",
"quote",
-
"syn 2.0.103",
+
"syn 2.0.106",
[[package]]
+1 -1
constellation/Cargo.toml
···
anyhow = "1.0.95"
askama = { version = "0.12.1", features = ["serde-json"] }
axum = "0.8.1"
-
axum-extra = { version = "0.10.0", features = ["typed-header"] }
+
axum-extra = { version = "0.10.0", features = ["query", "typed-header"] }
axum-metrics = "0.2"
bincode = "1.3.3"
clap = { version = "4.5.26", features = ["derive"] }
+2
constellation/src/bin/rocks-restore-from-backup.rs
···
use clap::Parser;
use std::path::PathBuf;
+
#[cfg(feature = "rocks")]
use rocksdb::backup::{BackupEngine, BackupEngineOptions, RestoreOptions};
use std::time;
···
to_data_dir: PathBuf,
}
+
#[cfg(feature = "rocks")]
fn main() -> Result<()> {
let args = Args::parse();
+47 -5
constellation/src/server/mod.rs
···
use bincode::Options;
use serde::{Deserialize, Serialize};
use serde_with::serde_as;
-
use std::collections::HashMap;
+
use std::collections::{HashMap, HashSet};
use std::time::{Duration, UNIX_EPOCH};
use tokio::net::{TcpListener, ToSocketAddrs};
use tokio::task::block_in_place;
···
collection: String,
path: String,
cursor: Option<OpaqueApiCursor>,
-
limit: Option<u64>,
+
/// Filter links only from these DIDs
+
///
+
/// include multiple times to filter by multiple source DIDs
+
#[serde(default)]
+
did: Vec<String>,
+
/// [deprecated] Filter links only from these DIDs
+
///
+
/// format: comma-separated sequence of DIDs
+
///
+
/// errors: if `did` parameter is also present
+
///
+
/// deprecated: use `did`, which can be repeated multiple times
+
from_dids: Option<String>, // comma separated: gross
+
#[serde(default = "get_default_limit")]
+
limit: u64,
// TODO: allow reverse (er, forward) order as well
+
}
+
fn get_default_limit() -> u64 {
+
DEFAULT_CURSOR_LIMIT
}
#[derive(Template, Serialize)]
#[template(path = "links.html.j2")]
···
}
fn get_links(
accept: ExtractAccept,
-
query: Query<GetLinkItemsQuery>,
+
query: axum_extra::extract::Query<GetLinkItemsQuery>, // supports multiple param occurrences
store: impl LinkReader,
) -> Result<impl IntoResponse, http::StatusCode> {
let until = query
···
.transpose()?
.map(|c| c.next);
-
let limit = query.limit.unwrap_or(DEFAULT_CURSOR_LIMIT);
+
let limit = query.limit;
if limit > DEFAULT_CURSOR_LIMIT_MAX {
return Err(http::StatusCode::BAD_REQUEST);
}
+
let mut filter_dids: HashSet<Did> = HashSet::from_iter(
+
query
+
.did
+
.iter()
+
.map(|d| d.trim())
+
.filter(|d| !d.is_empty())
+
.map(|d| Did(d.to_string())),
+
);
+
+
if let Some(comma_joined) = &query.from_dids {
+
if !filter_dids.is_empty() {
+
return Err(http::StatusCode::BAD_REQUEST);
+
}
+
for did in comma_joined.split(',') {
+
filter_dids.insert(Did(did.to_string()));
+
}
+
}
+
let paged = store
-
.get_links(&query.target, &query.collection, &query.path, limit, until)
+
.get_links(
+
&query.target,
+
&query.collection,
+
&query.path,
+
limit,
+
until,
+
&filter_dids,
+
)
.map_err(|_| http::StatusCode::INTERNAL_SERVER_ERROR)?;
let cursor = paged.next.map(|next| {
+15
constellation/src/storage/mem_store.rs
···
path: &str,
limit: u64,
until: Option<u64>,
+
filter_dids: &HashSet<Did>,
) -> Result<PagedAppendingCollection<RecordId>> {
let data = self.0.lock().unwrap();
let Some(paths) = data.targets.get(&Target::new(target)) else {
···
next: None,
total: 0,
});
+
};
+
+
let did_rkeys: Vec<_> = if !filter_dids.is_empty() {
+
did_rkeys
+
.iter()
+
.filter(|m| {
+
Option::<(Did, RKey)>::clone(m)
+
.map(|(did, _)| filter_dids.contains(&did))
+
.unwrap_or(false)
+
})
+
.cloned()
+
.collect()
+
} else {
+
did_rkeys.to_vec()
};
let total = did_rkeys.len();
+259 -14
constellation/src/storage/mod.rs
···
use crate::{ActionableEvent, CountsByCount, Did, RecordId};
use anyhow::Result;
use serde::{Deserialize, Serialize};
-
use std::collections::HashMap;
+
use std::collections::{HashMap, HashSet};
pub mod mem_store;
pub use mem_store::MemStorage;
···
path: &str,
limit: u64,
until: Option<u64>,
+
filter_dids: &HashSet<Did>,
) -> Result<PagedAppendingCollection<RecordId>>;
fn get_distinct_dids(
···
);
assert_eq!(storage.get_distinct_did_count("", "", "")?, 0);
assert_eq!(
-
storage.get_links("a.com", "app.t.c", ".abc.uri", 100, None)?,
+
storage.get_links(
+
"a.com",
+
"app.t.c",
+
".abc.uri",
+
100,
+
None,
+
&HashSet::default()
+
)?,
PagedAppendingCollection {
version: (0, 0),
items: vec![],
···
0,
)?;
assert_eq!(
-
storage.get_links("a.com", "app.t.c", ".abc.uri", 100, None)?,
+
storage.get_links(
+
"a.com",
+
"app.t.c",
+
".abc.uri",
+
100,
+
None,
+
&HashSet::default()
+
)?,
PagedAppendingCollection {
version: (1, 0),
items: vec![RecordId {
···
0,
)?;
}
-
let links = storage.get_links("a.com", "app.t.c", ".abc.uri", 2, None)?;
+
let links =
+
storage.get_links("a.com", "app.t.c", ".abc.uri", 2, None, &HashSet::default())?;
let dids = storage.get_distinct_dids("a.com", "app.t.c", ".abc.uri", 2, None)?;
assert_eq!(
links,
···
total: 5,
}
);
-
let links = storage.get_links("a.com", "app.t.c", ".abc.uri", 2, links.next)?;
+
let links = storage.get_links(
+
"a.com",
+
"app.t.c",
+
".abc.uri",
+
2,
+
links.next,
+
&HashSet::default(),
+
)?;
let dids = storage.get_distinct_dids("a.com", "app.t.c", ".abc.uri", 2, dids.next)?;
assert_eq!(
links,
···
total: 5,
}
);
-
let links = storage.get_links("a.com", "app.t.c", ".abc.uri", 2, links.next)?;
+
let links = storage.get_links(
+
"a.com",
+
"app.t.c",
+
".abc.uri",
+
2,
+
links.next,
+
&HashSet::default(),
+
)?;
let dids = storage.get_distinct_dids("a.com", "app.t.c", ".abc.uri", 2, dids.next)?;
assert_eq!(
links,
···
assert_stats(storage.get_stats()?, 5..=5, 1..=1, 5..=5);
});
+
test_each_storage!(get_filtered_links, |storage| {
+
let links = storage.get_links(
+
"a.com",
+
"app.t.c",
+
".abc.uri",
+
2,
+
None,
+
&HashSet::from([Did("did:plc:linker".to_string())]),
+
)?;
+
assert_eq!(
+
links,
+
PagedAppendingCollection {
+
version: (0, 0),
+
items: vec![],
+
next: None,
+
total: 0,
+
}
+
);
+
+
storage.push(
+
&ActionableEvent::CreateLinks {
+
record_id: RecordId {
+
did: "did:plc:linker".into(),
+
collection: "app.t.c".into(),
+
rkey: "asdf".into(),
+
},
+
links: vec![CollectedLink {
+
target: Link::Uri("a.com".into()),
+
path: ".abc.uri".into(),
+
}],
+
},
+
0,
+
)?;
+
+
let links = storage.get_links(
+
"a.com",
+
"app.t.c",
+
".abc.uri",
+
2,
+
None,
+
&HashSet::from([Did("did:plc:linker".to_string())]),
+
)?;
+
assert_eq!(
+
links,
+
PagedAppendingCollection {
+
version: (1, 0),
+
items: vec![RecordId {
+
did: "did:plc:linker".into(),
+
collection: "app.t.c".into(),
+
rkey: "asdf".into(),
+
},],
+
next: None,
+
total: 1,
+
}
+
);
+
+
let links = storage.get_links(
+
"a.com",
+
"app.t.c",
+
".abc.uri",
+
2,
+
None,
+
&HashSet::from([Did("did:plc:someone-else".to_string())]),
+
)?;
+
assert_eq!(
+
links,
+
PagedAppendingCollection {
+
version: (0, 0),
+
items: vec![],
+
next: None,
+
total: 0,
+
}
+
);
+
+
storage.push(
+
&ActionableEvent::CreateLinks {
+
record_id: RecordId {
+
did: "did:plc:linker".into(),
+
collection: "app.t.c".into(),
+
rkey: "asdf-2".into(),
+
},
+
links: vec![CollectedLink {
+
target: Link::Uri("a.com".into()),
+
path: ".abc.uri".into(),
+
}],
+
},
+
0,
+
)?;
+
storage.push(
+
&ActionableEvent::CreateLinks {
+
record_id: RecordId {
+
did: "did:plc:someone-else".into(),
+
collection: "app.t.c".into(),
+
rkey: "asdf".into(),
+
},
+
links: vec![CollectedLink {
+
target: Link::Uri("a.com".into()),
+
path: ".abc.uri".into(),
+
}],
+
},
+
0,
+
)?;
+
+
let links = storage.get_links(
+
"a.com",
+
"app.t.c",
+
".abc.uri",
+
2,
+
None,
+
&HashSet::from([Did("did:plc:linker".to_string())]),
+
)?;
+
assert_eq!(
+
links,
+
PagedAppendingCollection {
+
version: (2, 0),
+
items: vec![
+
RecordId {
+
did: "did:plc:linker".into(),
+
collection: "app.t.c".into(),
+
rkey: "asdf-2".into(),
+
},
+
RecordId {
+
did: "did:plc:linker".into(),
+
collection: "app.t.c".into(),
+
rkey: "asdf".into(),
+
},
+
],
+
next: None,
+
total: 2,
+
}
+
);
+
+
let links = storage.get_links(
+
"a.com",
+
"app.t.c",
+
".abc.uri",
+
2,
+
None,
+
&HashSet::from([
+
Did("did:plc:linker".to_string()),
+
Did("did:plc:someone-else".to_string()),
+
]),
+
)?;
+
assert_eq!(
+
links,
+
PagedAppendingCollection {
+
version: (3, 0),
+
items: vec![
+
RecordId {
+
did: "did:plc:someone-else".into(),
+
collection: "app.t.c".into(),
+
rkey: "asdf".into(),
+
},
+
RecordId {
+
did: "did:plc:linker".into(),
+
collection: "app.t.c".into(),
+
rkey: "asdf-2".into(),
+
},
+
],
+
next: Some(1),
+
total: 3,
+
}
+
);
+
+
let links = storage.get_links(
+
"a.com",
+
"app.t.c",
+
".abc.uri",
+
2,
+
None,
+
&HashSet::from([Did("did:plc:someone-unknown".to_string())]),
+
)?;
+
assert_eq!(
+
links,
+
PagedAppendingCollection {
+
version: (0, 0),
+
items: vec![],
+
next: None,
+
total: 0,
+
}
+
);
+
});
+
test_each_storage!(get_links_exact_multiple, |storage| {
for i in 1..=4 {
storage.push(
···
0,
)?;
}
-
let links = storage.get_links("a.com", "app.t.c", ".abc.uri", 2, None)?;
+
let links =
+
storage.get_links("a.com", "app.t.c", ".abc.uri", 2, None, &HashSet::default())?;
assert_eq!(
links,
PagedAppendingCollection {
···
total: 4,
}
);
-
let links = storage.get_links("a.com", "app.t.c", ".abc.uri", 2, links.next)?;
+
let links = storage.get_links(
+
"a.com",
+
"app.t.c",
+
".abc.uri",
+
2,
+
links.next,
+
&HashSet::default(),
+
)?;
assert_eq!(
links,
PagedAppendingCollection {
···
0,
)?;
}
-
let links = storage.get_links("a.com", "app.t.c", ".abc.uri", 2, None)?;
+
let links =
+
storage.get_links("a.com", "app.t.c", ".abc.uri", 2, None, &HashSet::default())?;
assert_eq!(
links,
PagedAppendingCollection {
···
},
0,
)?;
-
let links = storage.get_links("a.com", "app.t.c", ".abc.uri", 2, links.next)?;
+
let links = storage.get_links(
+
"a.com",
+
"app.t.c",
+
".abc.uri",
+
2,
+
links.next,
+
&HashSet::default(),
+
)?;
assert_eq!(
links,
PagedAppendingCollection {
···
0,
)?;
}
-
let links = storage.get_links("a.com", "app.t.c", ".abc.uri", 2, None)?;
+
let links =
+
storage.get_links("a.com", "app.t.c", ".abc.uri", 2, None, &HashSet::default())?;
assert_eq!(
links,
PagedAppendingCollection {
···
}),
0,
)?;
-
let links = storage.get_links("a.com", "app.t.c", ".abc.uri", 2, links.next)?;
+
let links = storage.get_links(
+
"a.com",
+
"app.t.c",
+
".abc.uri",
+
2,
+
links.next,
+
&HashSet::default(),
+
)?;
assert_eq!(
links,
PagedAppendingCollection {
···
0,
)?;
}
-
let links = storage.get_links("a.com", "app.t.c", ".abc.uri", 2, None)?;
+
let links =
+
storage.get_links("a.com", "app.t.c", ".abc.uri", 2, None, &HashSet::default())?;
assert_eq!(
links,
PagedAppendingCollection {
···
&ActionableEvent::DeactivateAccount("did:plc:asdf-1".into()),
0,
)?;
-
let links = storage.get_links("a.com", "app.t.c", ".abc.uri", 2, links.next)?;
+
let links = storage.get_links(
+
"a.com",
+
"app.t.c",
+
".abc.uri",
+
2,
+
links.next,
+
&HashSet::default(),
+
)?;
assert_eq!(
links,
PagedAppendingCollection {
+19 -1
constellation/src/storage/rocks_store.rs
···
path: &str,
limit: u64,
until: Option<u64>,
+
filter_dids: &HashSet<Did>,
) -> Result<PagedAppendingCollection<RecordId>> {
let target_key = TargetKey(
Target(target.to_string()),
···
});
};
-
let linkers = self.get_target_linkers(&target_id)?;
+
let mut linkers = self.get_target_linkers(&target_id)?;
+
if !filter_dids.is_empty() {
+
let mut did_filter = HashSet::new();
+
for did in filter_dids {
+
let Some(DidIdValue(did_id, active)) =
+
self.did_id_table.get_id_val(&self.db, did)?
+
else {
+
eprintln!("failed to find a did_id for {did:?}");
+
continue;
+
};
+
if !active {
+
eprintln!("excluding inactive did from filtered results");
+
continue;
+
}
+
did_filter.insert(did_id);
+
}
+
linkers.0.retain(|linker| did_filter.contains(&linker.0));
+
}
let (alive, gone) = linkers.count();
let total = alive + gone;
+9 -6
constellation/templates/hello.html.j2
···
<p>
This server has indexed <span class="stat">{{ stats.linking_records|human_number }}</span> links between <span class="stat">{{ stats.targetables|human_number }}</span> targets and sources from <span class="stat">{{ stats.dids|human_number }}</span> identities over <span class="stat">{{ days_indexed|human_number }}</span> days.<br/>
-
<small>(indexing new records in real time, backfill still TODO)</small>
+
<small>(indexing new records in real time, backfill coming soon!)</small>
</p>
-
<p>The API is currently <strong>unstable</strong>. But feel free to use it! If you want to be nice, put your project name and bsky username (or email) in your user-agent header for api requests.</p>
+
<p>But feel free to use it! If you want to be nice, put your project name and bsky username (or email) in your user-agent header for api requests.</p>
<h2>API Endpoints</h2>
···
<h4>Query parameters:</h4>
<ul>
-
<li><code>target</code>: required, must url-encode. Example: <code>at://did:plc:vc7f4oafdgxsihk4cry2xpze/app.bsky.feed.post/3lgwdn7vd722r</code></li>
-
<li><code>collection</code>: required. Example: <code>app.bsky.feed.like</code></li>
-
<li><code>path</code>: required, must url-encode. Example: <code>.subject.uri</code></li>
+
<li><p><code>target</code>: required, must url-encode. Example: <code>at://did:plc:vc7f4oafdgxsihk4cry2xpze/app.bsky.feed.post/3lgwdn7vd722r</code></p></li>
+
<li><p><code>collection</code>: required. Example: <code>app.bsky.feed.like</code></p></li>
+
<li><p><code>path</code>: required, must url-encode. Example: <code>.subject.uri</code></p></li>
+
<li><p><code>did</code>: optional, filter links to those from specific users. Include multiple times to filter by multiple users. Example: <code>did=did:plc:vc7f4oafdgxsihk4cry2xpze&did=did:plc:vc7f4oafdgxsihk4cry2xpze</code></p></li>
+
<li><p><code>from_dids</code> [deprecated]: optional. Use <code>did</code> instead. Example: <code>from_dids=did:plc:vc7f4oafdgxsihk4cry2xpze,did:plc:vc7f4oafdgxsihk4cry2xpze</code></p></li>
+
<li><p><code>limit</code>: optional. Default: <code>16</code>. Maximum: <code>100</code></p></li>
</ul>
<p style="margin-bottom: 0"><strong>Try it:</strong></p>
-
{% call try_it::links("at://did:plc:vc7f4oafdgxsihk4cry2xpze/app.bsky.feed.post/3lgwdn7vd722r", "app.bsky.feed.like", ".subject.uri") %}
+
{% call try_it::links("at://did:plc:a4pqq234yw7fqbddawjo7y35/app.bsky.feed.post/3m237ilwc372e", "app.bsky.feed.like", ".subject.uri", [""], 16) %}
<h3 class="route"><code>GET /links/distinct-dids</code></h3>
+1 -1
constellation/templates/links.html.j2
···
{% block content %}
-
{% call try_it::links(query.target, query.collection, query.path) %}
+
{% call try_it::links(query.target, query.collection, query.path, query.did, query.limit) %}
<h2>
Links to <code>{{ query.target }}</code>
+20 -2
constellation/templates/try-it-macros.html.j2
···
-
{% macro links(target, collection, path) %}
+
{% macro links(target, collection, path, dids, limit) %}
<form method="get" action="/links">
<pre class="code"><strong>GET</strong> /links
?target= <input type="text" name="target" value="{{ target }}" placeholder="target" />
&collection= <input type="text" name="collection" value="{{ collection }}" placeholder="collection" />
-
&path= <input type="text" name="path" value="{{ path }}" placeholder="path" /> <button type="submit">get links</button></pre>
+
&path= <input type="text" name="path" value="{{ path }}" placeholder="path" />
+
{%- for did in dids %}{% if !did.is_empty() %}
+
&did= <input type="text" name="did" value="{{ did }}" placeholder="did:plc:..." />{% endif %}{% endfor %}
+
<span id="did-placeholder"></span> <button id="add-did">+ did filter</button>
+
&limit= <input type="number" name="limit" value="{{ limit }}" max="100" placeholder="100" /> <button type="submit">get links</button></pre>
</form>
+
<script>
+
const addDidButton = document.getElementById('add-did');
+
const didPlaceholder = document.getElementById('did-placeholder');
+
addDidButton.addEventListener('click', e => {
+
e.preventDefault();
+
const i = document.createElement('input');
+
i.placeholder = 'did:plc:...';
+
i.name = "did"
+
const p = addDidButton.parentNode;
+
p.insertBefore(document.createTextNode('&did= '), didPlaceholder);
+
p.insertBefore(i, didPlaceholder);
+
p.insertBefore(document.createTextNode('\n '), didPlaceholder);
+
});
+
</script>
{% endmacro %}
+6 -7
reflector/src/main.rs
···
Data(parent): Data<&Option<String>>,
Query(AskQuery { domain }): Query<AskQuery>,
) -> Response {
-
if let Some(parent) = parent {
-
if let Some(prefix) = domain.strip_suffix(&format!(".{parent}")) {
-
if !prefix.contains('.') {
-
// no sub-sub-domains allowed
-
return Response::builder().body("ok");
-
}
-
}
+
if let Some(parent) = parent
+
&& let Some(prefix) = domain.strip_suffix(&format!(".{parent}"))
+
&& !prefix.contains('.')
+
{
+
// no sub-sub-domains allowed
+
return Response::builder().body("ok");
};
Response::builder()
.status(StatusCode::FORBIDDEN)
+5 -5
spacedust/src/subscriber.rs
···
loop {
tokio::select! {
l = receiver.recv() => match l {
-
Ok(link) => if self.filter(&link.properties) {
-
if let Err(e) = ws_sender.send(link.message.clone()).await {
-
log::warn!("failed to send link, dropping subscriber: {e:?}");
-
break;
-
}
+
Ok(link) => if self.filter(&link.properties)
+
&& let Err(e) = ws_sender.send(link.message.clone()).await
+
{
+
log::warn!("failed to send link, dropping subscriber: {e:?}");
+
break;
},
Err(RecvError::Closed) => self.shutdown.cancel(),
Err(RecvError::Lagged(n)) => {
+4 -4
who-am-i/src/server.rs
···
Some(parent_host),
);
}
-
if let Some(ref app) = params.app {
-
if !allowed_hosts.contains(app) {
-
return err("Login is not allowed for this app", false, Some(app));
-
}
+
if let Some(ref app) = params.app
+
&& !allowed_hosts.contains(app)
+
{
+
return err("Login is not allowed for this app", false, Some(app));
}
let parent_origin = url.origin().ascii_serialization();
if parent_origin == "null" {