1use ahash::RandomState;
2use async_trait::async_trait;
3use dashmap::DashMap;
4use reqwest::{Client, Url};
5use scraper::{Html, Selector};
6use serde::Serialize;
7
8use crate::{config::UserConfig, repo::Repository, types::RepoName, ExtractLog, Factory};
9
10#[derive(Serialize)]
11pub struct User {
12 pub name: String,
13 #[serde(skip)]
14 pub url: Url,
15 pub repo_count: usize,
16 #[serde(skip)]
17 pub pages_urls: Vec<Url>,
18 #[serde(skip)]
19 pub all_branches: bool,
20 pub repositories_data: DashMap<RepoName, Repository, RandomState>,
21}
22
23pub struct UserFactory {
24 url: Url,
25 name: String,
26 page_url: Url,
27 all_branches: bool,
28}
29
30impl UserFactory {
31 pub fn with_config(user_config: UserConfig) -> Self {
32 let url = user_config.url;
34 let all_branches: bool = user_config.all_branches;
35
36 let mut path_segment = url.path_segments().unwrap();
38 let name = path_segment.next().unwrap().to_string();
39
40 let page_url = format!("{}?tab=repositories&type=source", url);
41 let page_url = Url::parse(&page_url).unwrap();
42
43 UserFactory {
44 url,
45 name,
46 page_url,
47 all_branches,
48 }
49 }
50
51 pub async fn build_with_client(self, client: &Client) -> User {
52 let repo_count = Self::_repositories_count(client, self.page_url.clone()).await;
53 let pages_count = Self::_pages_count(repo_count);
54 let pages_urls = Self::_build_repo_links(self.page_url, repo_count, pages_count);
55
56 User {
57 name: self.name,
58 url: self.url,
59 repo_count,
60 pages_urls,
61 all_branches: self.all_branches,
62 repositories_data: DashMap::<_, _, RandomState>::with_capacity_and_hasher(
63 repo_count,
64 RandomState::new(),
65 ),
66 }
67 }
68}
69
70#[async_trait]
71impl Factory for UserFactory {
72 async fn _repositories_count(client: &Client, url: Url) -> usize {
73 let resp = client.get(url).send().await.unwrap();
74 let text = resp.text().await.unwrap();
75
76 let parser = Html::parse_document(&text);
77 let selector_repositories_count =
78 Selector::parse(r#"div > div.user-repo-search-results-summary > strong:nth-child(1)"#)
79 .unwrap();
80
81 let repository_count_str = parser
82 .select(&selector_repositories_count)
83 .next()
84 .unwrap()
85 .inner_html();
86
87 repository_count_str
88 .trim()
89 .replace(',', "")
90 .parse::<usize>()
91 .unwrap()
92 }
93}
94
95#[async_trait]
96impl ExtractLog for User {
97 async fn extract_log(mut self, client: &Client) -> Self {
98 let user_selector =
99 Selector::parse(r#"turbo-frame > div > div > ul > li > div > div > h3 > a"#).unwrap();
100
101 self.repositories_data = Self::common_log_feature(&self, client, user_selector).await;
102 self
103 }
104
105 fn get_repo_count(&self) -> usize {
106 self.repo_count
107 }
108
109 fn get_all_branches(&self) -> bool {
110 self.all_branches
111 }
112
113 fn get_url(&self) -> Url {
114 self.url.clone()
115 }
116
117 fn get_pages_url(&self) -> Vec<Url> {
118 self.pages_urls.clone()
119 }
120}