Two adjacent gaps surfaced while wiring the new frontend filter + sortable headers on /identities (feat/identities-filters branch). Bundling so backend gets one tracking item.
1. /identities orderBy is incomplete — filter exposes the columns, sort doesn't
The existing range filters in IdentitiesDAO.getIdentities already build WHERE clauses against total_data_contracts, total_documents, total_txs, total_transfers, balance (lines ~251‑280). So the columns are clearly accessible to the DAO — filter works, e.g. ?data_contracts_min=2 returns identities with ≥ 2 contracts.
But the ORDER BY switch at lines 231‑239 only branches on tx_count and balance:
const orderByOptions = [{ column: 'identity_id', order }]
if (orderBy === 'tx_count') orderByOptions.unshift({ column: 'total_txs', order })
if (orderBy === 'balance') orderByOptions.unshift({ column: 'balance', order })
Everything else (including documents_count and timestamp, which ARE in the paginationOptions.orderBy enum at packages/api/src/schemas.js#L28) silently falls through to identity_id ${order} — the request 200s but the rows aren't sorted by what the client asked for. data_contracts_count / transfers_count aren't even in the enum, so a request with those values 400s.
Net effect on the frontend list table: of 6 useful column headers (Identifier, Balance, Tx, Documents, Data Contracts, Timestamp), only two (Balance, Tx) actually sort correctly server‑side. Users can filter by Data Contracts via the existing filter pills but can't sort by it.
Suggested fix (10‑line change, zero new joins)
total_documents, total_data_contracts, total_transfers and blocks.timestamp are already selected by the existing query (lines 336‑337, line 361, line 366). Just teach the DAO orderBy switch to use them:
const sortColumns = {
tx_count: 'total_txs',
balance: 'balance',
documents_count: 'total_documents',
data_contracts_count: 'total_data_contracts',
transfers_count: 'total_transfers',
timestamp: 'timestamp'
}
const orderByOptions = [{ column: 'identity_id', order }]
if (sortColumns[orderBy]) orderByOptions.unshift({ column: sortColumns[orderBy], order })
And add data_contracts_count and transfers_count to the paginationOptions.orderBy enum at packages/api/src/schemas.js#L28. Symmetrical with what the filter side already does for those same columns.
2. /contestedResources has no filters
packages/api/src/routes.js#L270 wires the route to paginationOptions# only. Frontend can't filter the contested resources list at all today.
Proposed filter fields (mirror the /identities pattern)
status: enum 'pending' | 'finished' — matches the value in ContestedResource.js#L68
document_type_name: string — e.g. domain for DPNS contested names
data_contract_identifier: string (base58 id)
timestamp_min / timestamp_max: ISO 8601 — creation range
Optional, low priority: extend paginationOptions.orderBy enum with total_count_votes and end_timestamp so the frontend can sort contested resources by activity / time remaining.
Notes for implementation
ContestedResourcesDAO.js#L186 is non‑trivial — 6 nested CTEs with jsonb_each, jsonb_array_elements, ranking windows. The right place to apply WHERE clauses is probably the outer‑most from('ranked_documents') SELECT (~line 237) where data_contracts.identifier, documents.document_type_name, timestamp are all in scope. Worth a quick EXPLAIN ANALYZE before merge. status is computed from endTimestamp in the model, so server‑side filtering it likely needs WHERE end_timestamp ${'>' | '<='} NOW() rather than a stored column.
Frontend will mirror the just‑landed IdentitiesFilter once these fields are available.
Two adjacent gaps surfaced while wiring the new frontend filter + sortable headers on
/identities(feat/identities-filters branch). Bundling so backend gets one tracking item.1.
/identitiesorderByis incomplete — filter exposes the columns, sort doesn'tThe existing range filters in IdentitiesDAO.getIdentities already build WHERE clauses against
total_data_contracts,total_documents,total_txs,total_transfers,balance(lines ~251‑280). So the columns are clearly accessible to the DAO — filter works, e.g.?data_contracts_min=2returns identities with ≥ 2 contracts.But the ORDER BY switch at lines 231‑239 only branches on
tx_countandbalance:Everything else (including
documents_countandtimestamp, which ARE in thepaginationOptions.orderByenum at packages/api/src/schemas.js#L28) silently falls through toidentity_id ${order}— the request 200s but the rows aren't sorted by what the client asked for.data_contracts_count/transfers_countaren't even in the enum, so a request with those values 400s.Net effect on the frontend list table: of 6 useful column headers (Identifier, Balance, Tx, Documents, Data Contracts, Timestamp), only two (Balance, Tx) actually sort correctly server‑side. Users can filter by Data Contracts via the existing filter pills but can't sort by it.
Suggested fix (10‑line change, zero new joins)
total_documents,total_data_contracts,total_transfersandblocks.timestampare already selected by the existing query (lines 336‑337, line 361, line 366). Just teach the DAO orderBy switch to use them:And add
data_contracts_countandtransfers_countto thepaginationOptions.orderByenum at packages/api/src/schemas.js#L28. Symmetrical with what the filter side already does for those same columns.2.
/contestedResourceshas no filterspackages/api/src/routes.js#L270 wires the route to
paginationOptions#only. Frontend can't filter the contested resources list at all today.Proposed filter fields (mirror the
/identitiespattern)status: enum'pending' | 'finished'— matches the value in ContestedResource.js#L68document_type_name: string — e.g.domainfor DPNS contested namesdata_contract_identifier: string (base58 id)timestamp_min/timestamp_max: ISO 8601 — creation rangeOptional, low priority: extend
paginationOptions.orderByenum withtotal_count_votesandend_timestampso the frontend can sort contested resources by activity / time remaining.Notes for implementation
ContestedResourcesDAO.js#L186 is non‑trivial — 6 nested CTEs with
jsonb_each,jsonb_array_elements, ranking windows. The right place to apply WHERE clauses is probably the outer‑mostfrom('ranked_documents')SELECT (~line 237) wheredata_contracts.identifier,documents.document_type_name,timestampare all in scope. Worth a quickEXPLAIN ANALYZEbefore merge.statusis computed fromendTimestampin the model, so server‑side filtering it likely needsWHERE end_timestamp ${'>' | '<='} NOW()rather than a stored column.Frontend will mirror the just‑landed
IdentitiesFilteronce these fields are available.