Skip to main content

fatline_rs/
users.rs

1//! Types that are used by the various frontend implementations
2
3use async_trait::async_trait;
4use ed25519_dalek::SigningKey;
5use eyre::{bail, Result};
6use serde::{Deserialize, Serialize};
7use thiserror::Error;
8use tonic::Response;
9
10use crate::profile_builder::ProfileUpdate;
11use crate::proto::on_chain_event::Body;
12use crate::proto::{
13    FidRequest, Message, SignerEventType, SignerRequest, StorageLimitsResponse, UserDataRequest,
14    UserDataType,
15};
16use crate::to_messages::ToMessages;
17use crate::utils::{optional_get_user_data, optional_get_user_data_value};
18use crate::{HubService, PUBLIC_KEY_LENGTH};
19
20/// The combined user's profile, holding values from all user update types
21#[derive(Clone, Debug, Serialize, Deserialize, Default)]
22pub struct Profile {
23    pub fid: u64,
24    pub username: Option<String>,
25    pub display_name: Option<String>,
26    pub profile_picture: Option<String>,
27    pub bio: Option<String>,
28    pub url: Option<String>,
29}
30
31#[derive(Error, Debug)]
32pub enum UserError {
33    #[error("no storage available")]
34    NoStorage,
35}
36
37/// A service to get merged user info form a hubble grpc service
38#[async_trait]
39pub trait UserService {
40    /// Combine all the user's data messages into a serializable Profile type; might be default for values that aren't set
41    async fn get_user_profile(&mut self, fid: u64) -> Result<Profile>;
42    /// Checks that the key is *currently* valid for the fid, returning false or error in every other case
43    async fn key_valid_for_fid(&mut self, key: &[u8; PUBLIC_KEY_LENGTH], fid: u64) -> Result<bool>;
44    /// Gets a list of profiles that *follow* the user. maybe paginate this
45    async fn get_user_followers(&mut self, fid: u64) -> Result<Vec<Profile>>;
46    /// Get a list of profiles that the user is *following*. maybe paginate this
47    async fn get_user_following(&mut self, fid: u64) -> Result<Vec<Profile>>;
48    /// Seek through all follow events until none remain
49    async fn get_all_follow_events(&mut self, fid: u64) -> Result<Vec<u64>>;
50    // /// Subscribe to all user events
51    // async fn get_all_user_events(&mut self, fid: Option<u64>) -> Result<impl Stream<Item = HubEvent>>;
52    async fn update_user(
53        &mut self,
54        signing_key: &SigningKey,
55        fid: u64,
56        update: ProfileUpdate,
57    ) -> Result<Profile>;
58}
59
60fn get_user_data_request(fid: u64, data_type: UserDataType) -> UserDataRequest {
61    UserDataRequest {
62        fid,
63        user_data_type: data_type as i32,
64    }
65}
66
67fn get_user_data(response: Response<Message>) -> Option<String> {
68    optional_get_user_data(response).and_then(optional_get_user_data_value)
69}
70
71fn get_reaction_data(response: Response<Message>) -> Option<u64> {
72    todo!()
73    // optional_get_follow_message(&response.into_inner()).and_then(optional_get_followed_fid)
74}
75
76fn contains_any_storage_units(response: Response<StorageLimitsResponse>) -> bool {
77    response.into_inner().units > 0
78}
79
80#[async_trait]
81impl UserService for HubService {
82    async fn get_user_profile(&mut self, fid: u64) -> Result<Profile> {
83        // Check fid is valid, save subsequent calls if we are looking up non-existent data
84        let fid_check = self
85            .get_current_storage_limits_by_fid(FidRequest {
86                fid,
87                page_size: Some(1),
88                page_token: None,
89                reverse: None,
90            })
91            .await
92            .is_ok_and(contains_any_storage_units);
93
94        if !fid_check {
95            bail!(UserError::NoStorage)
96        }
97
98        // Username request
99        let username = self
100            .get_user_data(get_user_data_request(fid, UserDataType::Username))
101            .await
102            .map_or(None, get_user_data);
103
104        // Display name request
105        let display_name = self
106            .get_user_data(get_user_data_request(fid, UserDataType::Display))
107            .await
108            .map_or(None, get_user_data);
109
110        // Profile pictue URL request
111        let profile_picture = self
112            .get_user_data(get_user_data_request(fid, UserDataType::Pfp))
113            .await
114            .map_or(None, get_user_data);
115
116        // Bio request
117        let bio = self
118            .get_user_data(get_user_data_request(fid, UserDataType::Bio))
119            .await
120            .map_or(None, get_user_data);
121
122        // Extra profile URL request
123        let url = self
124            .get_user_data(get_user_data_request(fid, UserDataType::Url))
125            .await
126            .map_or(None, get_user_data);
127
128        // Concat all data together, technically everything except fid here is optional
129        Ok(Profile {
130            fid,
131            username,
132            display_name,
133            profile_picture,
134            bio,
135            url,
136        })
137    }
138
139    async fn key_valid_for_fid(&mut self, key: &[u8; PUBLIC_KEY_LENGTH], fid: u64) -> Result<bool> {
140        let onchain_event = self
141            .get_on_chain_signer(SignerRequest {
142                fid: fid,
143                signer: key.to_vec(),
144            })
145            .await?
146            .into_inner();
147
148        match onchain_event.body {
149            Some(Body::SignerEventBody(body)) => {
150                // return bool of whether the signer event was an add
151                // a remove signifies that the signer isn't valid anymore
152                Ok(body.event_type == SignerEventType::Add as i32)
153            }
154            _ => Ok(false),
155        }
156    }
157
158    async fn get_user_followers(&mut self, fid: u64) -> Result<Vec<Profile>> {
159        todo!()
160        // self.get_links_by_fid()
161    }
162
163    async fn get_user_following(&mut self, fid: u64) -> Result<Vec<Profile>> {
164        let all_followed = self.get_all_follow_events(fid).await?;
165        todo!()
166    }
167
168    async fn get_all_follow_events(&mut self, fid: u64) -> Result<Vec<u64>> {
169        // let mut page = None;
170        // let results = Vec::new();
171        // while page.is_some() || results.is_empty() {
172        //     let res = self.get_all_cast_messages_by_fid(FidRequest {
173        //         page_token: page.clone(),
174        //         fid,
175        //         reverse: None,
176        //         page_size: None
177        //     }).await?;
178        //     let inner = res.into_inner();
179        //     page = inner.next_page_token;
180        // TODO: need to actually map the follow to unfollow adds to removes
181        // let all_follows = inner.messages
182        //     .iter()
183        //     .map();
184        // }
185        todo!()
186    }
187
188    async fn update_user(
189        &mut self,
190        signing_key: &SigningKey,
191        fid: u64,
192        update: ProfileUpdate,
193    ) -> Result<Profile> {
194        let messages = update.to_messages(fid, &signing_key);
195        for message in messages {
196            self.submit_message(message).await?;
197        }
198        self.get_user_profile(fid).await
199    }
200
201    // async fn get_all_user_events(&mut self, fid: Option<u64>) -> Result<impl Stream<Item = HubEvent>> {
202    //     todo!()
203    // }
204}