ido: filter list_ido_entries by owner_nfthash
Add an optional owner_nfthash query param to GET /<id>/entries. Accepts up to 20 comma-delimited 32-byte hex hashes; an entry matches any listed value via an owner_nfthash IN (...) clause. Rejects malformed hex, wrong-length values, and >20 filters with a 400. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
aec0a6f0f8
commit
d5bdf5a740
2 changed files with 64 additions and 2 deletions
|
|
@ -2668,6 +2668,7 @@ pub async fn list_ido_entries(
|
|||
internal_id: i64,
|
||||
preinit_txid_hex: &str,
|
||||
distributed: Option<bool>,
|
||||
owner_nfthashes: &[Vec<u8>],
|
||||
offset: i64,
|
||||
limit: i64,
|
||||
) -> Result<Vec<IdoEntryRpcRecord>> {
|
||||
|
|
@ -2680,6 +2681,14 @@ pub async fn list_ido_entries(
|
|||
qb.push(" AND distributed = ");
|
||||
qb.push_bind(v as i64);
|
||||
}
|
||||
if !owner_nfthashes.is_empty() {
|
||||
qb.push(" AND owner_nfthash IN (");
|
||||
let mut separated = qb.separated(", ");
|
||||
for hash in owner_nfthashes {
|
||||
separated.push_bind(hash.clone());
|
||||
}
|
||||
separated.push_unseparated(")");
|
||||
}
|
||||
qb.push(" ORDER BY rowid ASC LIMIT ");
|
||||
qb.push_bind(limit);
|
||||
qb.push(" OFFSET ");
|
||||
|
|
|
|||
|
|
@ -14,6 +14,12 @@ use serde_json::Value;
|
|||
const LIST_DEFAULT_LIMIT: i64 = 20;
|
||||
const LIST_MAX_LIMIT: i64 = 100;
|
||||
|
||||
/// Maximum number of comma-delimited owner_nfthash filters accepted per request.
|
||||
const MAX_OWNER_NFTHASH_FILTERS: usize = 20;
|
||||
|
||||
/// Length in bytes of an owner_nfthash.
|
||||
const OWNER_NFTHASH_LEN: usize = 32;
|
||||
|
||||
const DEBUG_DEFAULT_LIMIT: i64 = 100;
|
||||
const DEBUG_MAX_LIMIT: i64 = 10_000;
|
||||
|
||||
|
|
@ -96,16 +102,61 @@ pub async fn get_ido_by_offering_token(
|
|||
Ok(cached_ok(serde_json::to_value(item).unwrap(), CACHE_NONE))
|
||||
}
|
||||
|
||||
/// Parse the comma-delimited `owner_nfthash` query parameter into raw byte
|
||||
/// filters. Returns an empty vec when the parameter is absent. Rejects more
|
||||
/// than [`MAX_OWNER_NFTHASH_FILTERS`] values, blank entries, and invalid hex.
|
||||
fn parse_owner_nfthash_filters(
|
||||
owner_nfthash: Option<&str>,
|
||||
) -> Result<Vec<Vec<u8>>, rocket::response::status::Custom<rocket::serde::json::Json<Value>>> {
|
||||
let Some(raw) = owner_nfthash else {
|
||||
return Ok(Vec::new());
|
||||
};
|
||||
|
||||
let parts: Vec<&str> = raw.split(',').map(|s| s.trim()).filter(|s| !s.is_empty()).collect();
|
||||
if parts.len() > MAX_OWNER_NFTHASH_FILTERS {
|
||||
return Err(bad_request(
|
||||
ApiErrorCode::InvalidParameters,
|
||||
&format!("Too many owner_nfthash filters (max {MAX_OWNER_NFTHASH_FILTERS})"),
|
||||
));
|
||||
}
|
||||
|
||||
parts
|
||||
.into_iter()
|
||||
.map(|s| {
|
||||
let bytes = hex::decode(s).map_err(|e| {
|
||||
bad_request(
|
||||
ApiErrorCode::InvalidParameters,
|
||||
&format!("Invalid owner_nfthash '{s}': {e}"),
|
||||
)
|
||||
})?;
|
||||
if bytes.len() != OWNER_NFTHASH_LEN {
|
||||
return Err(bad_request(
|
||||
ApiErrorCode::InvalidParameters,
|
||||
&format!(
|
||||
"Invalid owner_nfthash '{s}': expected {OWNER_NFTHASH_LEN} bytes, got {}",
|
||||
bytes.len()
|
||||
),
|
||||
));
|
||||
}
|
||||
Ok(bytes)
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// List entries for an IDO.
|
||||
///
|
||||
/// - `id`: the IDO's public id (preinit txid, 64-char hex)
|
||||
/// - `distributed`: optional boolean filter
|
||||
/// - `owner_nfthash`: optional filter by owner nfthash (hex). Multiple hashes
|
||||
/// may be supplied comma-delimited; up to 20 per request. An entry matches
|
||||
/// if its owner_nfthash equals any of the supplied values.
|
||||
///
|
||||
/// Pagination: `offset` (default 0), `limit` (default 20, max 100)
|
||||
#[get("/<id>/entries?<distributed>&<offset>&<limit>")]
|
||||
#[get("/<id>/entries?<distributed>&<owner_nfthash>&<offset>&<limit>")]
|
||||
pub async fn list_ido_entries(
|
||||
id: &str,
|
||||
distributed: Option<bool>,
|
||||
owner_nfthash: Option<&str>,
|
||||
offset: Option<i64>,
|
||||
limit: Option<i64>,
|
||||
db: &State<DB>,
|
||||
|
|
@ -113,9 +164,11 @@ pub async fn list_ido_entries(
|
|||
let offset = offset.unwrap_or(0).max(0);
|
||||
let limit = limit.unwrap_or(LIST_DEFAULT_LIMIT).clamp(1, LIST_MAX_LIMIT);
|
||||
|
||||
let owner_nfthashes = parse_owner_nfthash_filters(owner_nfthash)?;
|
||||
|
||||
let (internal_id, preinit_txid_hex) = resolve_ido(id, &db.ido_r).await?;
|
||||
|
||||
let items = crate::db::ido::list_ido_entries(&db.ido_r, internal_id, &preinit_txid_hex, distributed, offset, limit)
|
||||
let items = crate::db::ido::list_ido_entries(&db.ido_r, internal_id, &preinit_txid_hex, distributed, &owner_nfthashes, offset, limit)
|
||||
.await
|
||||
.map_err(db_error)?;
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue