@@ -9,6 +9,7 @@ use serde::Deserialize;
99use crate :: auth:: AuthenticatedDid ;
1010use crate :: error:: { AppError , Result } ;
1111use crate :: state:: AppState ;
12+ use crate :: visibility:: { visibility_check, Decision } ;
1213
1314#[ derive( Debug , Deserialize ) ]
1415pub struct ListAnchorsQuery {
@@ -67,33 +68,54 @@ pub async fn list_anchors(
6768 } ;
6869
6970 let limit = q. limit . clamp ( 0 , 200 ) ;
70- let raw_anchors = state
71- . db
72- . list_arweave_anchors ( normalized_repo. as_deref ( ) , limit)
73- . await
74- . map_err ( AppError :: Internal ) ?;
7571
76- // For global listings (no ?repo=), filter each anchor on current visibility.
77- let anchors = if normalized_repo. is_none ( ) {
78- let mut filtered = Vec :: new ( ) ;
79- for anchor in raw_anchors {
80- // Parse repo slug to resolve current visibility.
81- let parts: Vec < & str > = anchor. repo . splitn ( 2 , '/' ) . collect ( ) ;
82- if parts. len ( ) != 2 {
83- continue ;
84- }
85- let ( owner, name) = ( parts[ 0 ] , parts[ 1 ] ) ;
86- // Skip anchors for repos the caller cannot currently read.
87- if crate :: api:: authorize_repo_read ( & state, owner, name, caller, "/" )
88- . await
89- . is_ok ( )
90- {
91- filtered. push ( anchor) ;
92- }
93- }
94- filtered
72+ let anchors = if let Some ( ref slug) = normalized_repo {
73+ // Per-repo: filter at the DB level and return directly.
74+ state
75+ . db
76+ . list_arweave_anchors ( Some ( slug) , limit)
77+ . await
78+ . map_err ( AppError :: Internal ) ?
9579 } else {
96- raw_anchors
80+ // Global listing: fetch all anchors, filter by current visibility
81+ // against the deduped repo view (so mirror rows never bypass canonical
82+ // visibility), then take limit.
83+ let all_anchors = state
84+ . db
85+ . list_arweave_anchors ( None , i64:: MAX )
86+ . await
87+ . map_err ( AppError :: Internal ) ?;
88+
89+ // Build a set of readable repo short-form slugs from the deduped list.
90+ let repos = state
91+ . db
92+ . list_all_repos_deduped ( )
93+ . await
94+ . map_err ( AppError :: Internal ) ?;
95+ let repo_ids: Vec < String > = repos. iter ( ) . map ( |r| r. id . clone ( ) ) . collect ( ) ;
96+ let rules_by_repo = state
97+ . db
98+ . list_visibility_rules_for_repos ( & repo_ids)
99+ . await
100+ . map_err ( AppError :: Internal ) ?;
101+
102+ let readable: std:: collections:: HashSet < String > = repos
103+ . iter ( )
104+ . filter ( |r| {
105+ let rules = rules_by_repo. get ( & r. id ) . map ( Vec :: as_slice) . unwrap_or ( & [ ] ) ;
106+ visibility_check ( rules, r. is_public , & r. owner_did , caller, "/" ) != Decision :: Deny
107+ } )
108+ . map ( |r| {
109+ let short = r. owner_did . split ( ':' ) . next_back ( ) . unwrap_or ( & r. owner_did ) ;
110+ format ! ( "{}/{}" , short, r. name)
111+ } )
112+ . collect ( ) ;
113+
114+ all_anchors
115+ . into_iter ( )
116+ . filter ( |a| readable. contains ( & a. repo ) )
117+ . take ( limit as usize )
118+ . collect ( )
97119 } ;
98120
99121 Ok ( Json ( serde_json:: json!( {
0 commit comments