~ knotless Search/
↑↓ move↵ open⇧↵ all results

chadtmiller.com/knotless

git that lives in your Atmosphere account

knotless.dev
1 collaborator

knotless 0.13.0 Latest 2 packages, 1 image

1
10

An author: filter reads kept handles rather than asking each time

Merged chadtmiller.com asked to merge into main from chadtmiller.com/knotless:serve/author-filter
crates/knotless-serve/src/web/items.rs+14−4
@@ -98,12 +98,22 @@ pub async fn list_items(
}
/// The DID a filter's `author:` names, by handle or DID; as written if it
/// resolves to none, so it matches no one.
/// resolves to none, so it matches no one. A handle the index found for an
/// account is that account's; any other is looked up, and what was found
/// kept a while, as a crawler asks for the same filter again and again and
/// a handle's host can take seconds to answer.
async fn did(app: &App, author: &str) -> String {
match knotless::identity::resolve_handle(&app.ctx, author).await {
Ok(did) => did,
Err(_) => author.to_string(),
let indexed = app.index.as_ref().map(|i| i.did_of_handle(author));
if let Some(Ok(Some(did))) = indexed {
return did;
}
let (ctx, handle) = (app.ctx.clone(), author.to_string());
let look = async move {
let found = knotless::identity::resolve_handle(&ctx, &handle).await;
Some(found.ok())
crates/knotless-serve/src/web/items.rsline 113

found.ok() makes any error, including a timeout or 5xx, into Some(None), which through stores for ten minutes. Only a real "names no one" answer should be kept. Return None for the other errors.

};
let found = AUTHORS.through(&app.ctx.env.cache_dir, author, look).await;
crates/knotless-serve/src/web/items.rsline 115

author is user input used as the cache key. Each distinct value adds an entry and rewrites all of authors.json, with no bound. Check that it looks like a handle before it reaches the cache, and/or cap the size.

found.flatten().unwrap_or(author.to_string())
}
#[derive(Deserialize)]
crates/knotless-serve/src/index.rs+10−0
@@ -630,6 +630,7 @@ pub(crate) fn connect(path: &Path) -> rusqlite::Result<(Connection, bool)> {
value TEXT NOT NULL, PRIMARY KEY (did, collection, rkey));
CREATE TABLE IF NOT EXISTS handle (
did TEXT PRIMARY KEY, handle TEXT, checked INTEGER NOT NULL);
CREATE INDEX IF NOT EXISTS handle_name ON handle (handle);
CREATE TABLE IF NOT EXISTS home (
did TEXT PRIMARY KEY, moved INTEGER NOT NULL, own INTEGER NOT NULL,
checked INTEGER NOT NULL);
@@ -3371,6 +3372,15 @@ impl Index {
.optional()
}
/// The account whose handle was last found to be `handle`, if any was.
pub fn did_of_handle(&self, handle: &str) -> rusqlite::Result<Option<String>> {
let db = self.db.lock().unwrap();
let mut stmt = db.prepare_cached(
"SELECT did FROM handle WHERE handle = ?1 ORDER BY checked DESC LIMIT 1",
)?;
stmt.query_row([handle], |row| row.get(0)).optional()
}
/// Keeps `handle` as what `did`'s handle was found to be at `checked`.
pub fn set_handle(
&self,
crates/knotless-serve/src/web.rs+4−0
@@ -613,6 +613,10 @@ static AVATARS: LazyLock<Kept<Option<String>>> =
LazyLock::new(|| Kept::new("avatars.json", KEPT_FOR, LOOKUP_WAIT));
static CLAIMS: LazyLock<Kept<Vec<String>>> =
LazyLock::new(|| Kept::new("claims.json", KEPT_FOR, LOOKUP_WAIT));
/// The accounts `author:` filters named by handle, by handle: none for a
/// handle that names none.
static AUTHORS: LazyLock<Kept<Option<String>>> =
LazyLock::new(|| Kept::new("authors.json", KEPT_FOR, LOOKUP_WAIT));
/// Where Bluesky's CDN serves profile pictures, by DID and blob CID.
const AVATAR_CDN: &str = "https://cdn.bsky.app/img/avatar/plain";
docs/design/serve.md+6−0
@@ -146,6 +146,12 @@ is kept looks it up again at once: the event names the new handle, but is not
trusted for it. One kept a day is still shown, and looked up again behind
the page showing it. Without an index, handles are kept in memory.
A filter of pulls or issues by `author:` reads the account from those kept
handles. A handle not kept there is looked up at its host, and what was
found, an account or none, is kept for ten minutes, in the cache directory
too. A crawler can ask for the same filter every minute, and a handle's
host can take seconds to answer.
### Accounts that go
An account event says whether the account is active at its host and, if