1+ use std:: collections:: HashMap ;
2+
13use axum:: async_trait;
24use enstate_shared:: core:: Profile ;
35use enstate_shared:: discovery:: Discovery ;
6+ use ethers:: providers:: namehash;
7+ use serde:: { Deserialize , Serialize } ;
48
59pub struct DiscoveryEngine {
6- client : clickhouse:: Client ,
10+ client : meilisearch_sdk:: client:: Client ,
11+ }
12+
13+ #[ derive( Debug , Serialize , Deserialize ) ]
14+ pub struct MeiliProfileDocument {
15+ name_hash : String ,
16+ name : String ,
17+ avatar : Option < String > ,
18+ header : Option < String > ,
19+ display : String ,
20+ addresses : HashMap < String , String > ,
21+ fresh : i64 ,
22+ }
23+
24+ impl From < & Profile > for MeiliProfileDocument {
25+ fn from ( profile : & Profile ) -> Self {
26+ Self {
27+ name_hash : format ! ( "{:x}" , namehash( & profile. name) ) ,
28+ name : profile. name . clone ( ) ,
29+ avatar : profile. avatar . clone ( ) ,
30+ header : profile. header . clone ( ) ,
31+ display : profile. display . clone ( ) ,
32+ addresses : profile. chains . iter ( ) . map ( |( chain, address) | ( chain. to_string ( ) , address. to_string ( ) ) ) . collect ( ) ,
33+ fresh : profile. fresh ,
34+ }
35+ }
736}
837
938impl DiscoveryEngine {
10- pub fn new ( url : & str , user : & str , password : & str ) -> Self {
39+ pub fn new ( url : & str , key : Option < & str > ) -> Self {
1140 Self {
12- client : clickhouse :: Client :: default ( ) . with_url ( url) . with_user ( user ) . with_password ( password ) ,
41+ client : meilisearch_sdk :: client :: Client :: new ( url, key ) . unwrap ( ) ,
1342 }
1443 }
1544
1645 pub async fn create_table_if_not_exists ( & self ) -> Result < ( ) , ( ) > {
17- // Create the profiles table
18- let profiles_table_query = self . client . query ( "CREATE TABLE IF NOT EXISTS enstate.profiles (name String, address String, avatar String, header String, display String, contenthash String, resolver String, ccip_urls Array(String), errors Array(String), fresh Int64) ENGINE = ReplacingMergeTree(fresh) ORDER BY name" ) ;
19-
20- match profiles_table_query. execute ( ) . await {
21- Ok ( _) => { } ,
22- Err ( e) => {
23- tracing:: error!( "Error creating profiles table: {}" , e) ;
24- return Err ( ( ) ) ;
25- }
26- } ;
2746
28- // Create the stats aggregation table
29- let stats_table_query = self . client . query ( "
30- CREATE TABLE IF NOT EXISTS enstate.profile_stats_agg
31- (
32- day Date,
33- total_profiles UInt64,
34- profiles_with_avatar UInt64,
35- profiles_with_header UInt64,
36- profiles_with_display UInt64,
37- profiles_with_contenthash UInt64
38- )
39- ENGINE = SummingMergeTree
40- PARTITION BY day
41- ORDER BY day
42- " ) ;
43-
44- match stats_table_query. execute ( ) . await {
45- Ok ( _) => { } ,
46- Err ( e) => {
47- tracing:: error!( "Error creating profile_stats_agg table: {}" , e) ;
48- return Err ( ( ) ) ;
49- }
50- } ;
51-
52- // Create the materialized view
53- let materialized_view_query = self . client . query ( "
54- CREATE MATERIALIZED VIEW IF NOT EXISTS enstate.mv_profile_stats_agg
55- TO enstate.profile_stats_agg
56- AS
57- SELECT
58- toDate(fromUnixTimestamp64Milli(fresh)) AS day,
59- count() AS total_profiles,
60- countIf(avatar != '') AS profiles_with_avatar,
61- countIf(header != '') AS profiles_with_header,
62- countIf(display != '') AS profiles_with_display,
63- countIf(contenthash != '') AS profiles_with_contenthash
64- FROM enstate.profiles
65- GROUP BY day
66- " ) ;
67-
68- match materialized_view_query. execute ( ) . await {
69- Ok ( _) => { } ,
70- Err ( e) => {
71- tracing:: error!( "Error creating materialized view: {}" , e) ;
72- return Err ( ( ) ) ;
73- }
74- } ;
7547
7648 Ok ( ( ) )
7749 }
@@ -88,21 +60,31 @@ impl Discovery for DiscoveryEngine {
8860 . map ( |( key, val) | format ! ( "{}: {}" , key, val) )
8961 . collect ( ) ;
9062
91- let query = self . client
92- . query ( "INSERT INTO enstate.profiles (name, address, avatar, header, display, contenthash, resolver, ccip_urls, errors, fresh) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)" )
93- . bind ( profile. name . clone ( ) )
94- . bind ( profile. address . as_ref ( ) . map ( |x| x. to_string ( ) ) . unwrap_or_default ( ) )
95- . bind ( profile. avatar . as_deref ( ) . unwrap_or_default ( ) )
96- . bind ( profile. header . as_deref ( ) . unwrap_or_default ( ) )
97- . bind ( profile. display . clone ( ) )
98- . bind ( profile. contenthash . as_deref ( ) . unwrap_or_default ( ) )
99- . bind ( profile. resolver . clone ( ) )
100- . bind ( ccip_urls)
101- . bind ( errors)
102- . bind ( profile. fresh ) ;
63+ // let query = self.client
64+ // .query("INSERT INTO enstate.profiles (name, address, avatar, header, display, contenthash, resolver, ccip_urls, errors, fresh) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)")
65+ // .bind(profile.name.clone())
66+ // .bind(profile.address.as_ref().map(|x| x.to_string()).unwrap_or_default())
67+ // .bind(profile.avatar.as_deref().unwrap_or_default())
68+ // .bind(profile.header.as_deref().unwrap_or_default())
69+ // .bind(profile.display.clone())
70+ // .bind(profile.contenthash.as_deref().unwrap_or_default())
71+ // .bind(profile.resolver.clone())
72+ // .bind(ccip_urls)
73+ // .bind(errors)
74+ // .bind(profile.fresh);
75+
76+ let document = MeiliProfileDocument :: from ( profile) ;
77+
78+ let documents = vec ! [ document] ;
79+
80+ let x = self . client . index ( "profiles" ) ;
81+ let x = x. add_documents ( & documents, Some ( "name_hash" ) ) . await ;
10382
104- match query. execute ( ) . await {
105- Ok ( _) => Ok ( ( ) ) ,
83+ match x {
84+ Ok ( result) => {
85+ tracing:: info!( "Inserted profile: {:?}" , result) ;
86+ Ok ( ( ) )
87+ } ,
10688 Err ( e) => {
10789 tracing:: error!( "Error inserting profile: {}" , e) ;
10890 Err ( ( ) )
0 commit comments