A lexicon-driven AppView for ATProto. happyview.dev
backfill firehose jetstream atproto appview oauth lexicon
8
fork

Configure Feed

Select the types of activity you want to include in your feed.

feat: add `db.backlinks` for filtering results based on backlinks

Trezy 88073d83 6406c7a9

+76
+76
src/lua/db_api.rs
··· 179 179 })?; 180 180 db_table.set("count", count_fn)?; 181 181 182 + // db.backlinks({ collection, uri, did?, limit?, offset? }) -> { records, cursor? } 183 + // Find records in `collection` whose JSONB contains the given AT URI. 184 + let state_backlinks = state.clone(); 185 + let backlinks_fn = lua.create_async_function(move |lua, opts: mlua::Table| { 186 + let state = state_backlinks.clone(); 187 + async move { 188 + let collection: String = opts.get("collection")?; 189 + let uri: String = opts.get("uri")?; 190 + let did: Option<String> = opts.get("did").ok(); 191 + let limit: i64 = opts.get::<i64>("limit").unwrap_or(20).min(100); 192 + let offset: i64 = opts.get::<i64>("offset").unwrap_or(0); 193 + 194 + let rows: Vec<(String, String, Value)> = if let Some(ref did) = did { 195 + sqlx::query_as( 196 + "SELECT uri, did, record FROM records \ 197 + WHERE collection = $1 \ 198 + AND record::text LIKE '%' || $2 || '%' \ 199 + AND did = $3 \ 200 + ORDER BY indexed_at DESC \ 201 + LIMIT $4 OFFSET $5", 202 + ) 203 + .bind(&collection) 204 + .bind(&uri) 205 + .bind(did) 206 + .bind(limit) 207 + .bind(offset) 208 + .fetch_all(&state.db) 209 + .await 210 + .map_err(|e| mlua::Error::runtime(format!("DB backlinks failed: {e}")))? 211 + } else { 212 + sqlx::query_as( 213 + "SELECT uri, did, record FROM records \ 214 + WHERE collection = $1 \ 215 + AND record::text LIKE '%' || $2 || '%' \ 216 + ORDER BY indexed_at DESC \ 217 + LIMIT $3 OFFSET $4", 218 + ) 219 + .bind(&collection) 220 + .bind(&uri) 221 + .bind(limit) 222 + .bind(offset) 223 + .fetch_all(&state.db) 224 + .await 225 + .map_err(|e| mlua::Error::runtime(format!("DB backlinks failed: {e}")))? 226 + }; 227 + 228 + let has_next = rows.len() as i64 == limit; 229 + let records: Vec<Value> = rows 230 + .into_iter() 231 + .map(|(uri, _did, mut record)| { 232 + if let Some(obj) = record.as_object_mut() { 233 + obj.insert("uri".to_string(), json!(uri)); 234 + } 235 + record 236 + }) 237 + .collect(); 238 + 239 + let record_values: Vec<mlua::Value> = records 240 + .iter() 241 + .map(|r| lua.to_value(r)) 242 + .collect::<LuaResult<_>>()?; 243 + let records_table = lua.create_sequence_from(record_values)?; 244 + records_table.set_metatable(Some(lua.array_metatable()))?; 245 + 246 + let result_table = lua.create_table()?; 247 + result_table.set("records", records_table)?; 248 + if has_next { 249 + let next_cursor = (offset + limit).to_string(); 250 + result_table.set("cursor", next_cursor)?; 251 + } 252 + 253 + Ok(mlua::Value::Table(result_table)) 254 + } 255 + })?; 256 + db_table.set("backlinks", backlinks_fn)?; 257 + 182 258 // db.raw(sql, params?) -> rows[] 183 259 // Read-only: only SELECT statements are allowed. 184 260 let state_raw = state;