1use super::*;
2
3impl Database {
4 pub async fn get_extensions(
5 &self,
6 filter: Option<&str>,
7 limit: usize,
8 ) -> Result<Vec<ExtensionMetadata>> {
9 self.transaction(|tx| async move {
10 let mut condition = Condition::all();
11 if let Some(filter) = filter {
12 let fuzzy_name_filter = Self::fuzzy_like_string(filter);
13 condition = condition.add(Expr::cust_with_expr("name ILIKE $1", fuzzy_name_filter));
14 }
15
16 let extensions = extension::Entity::find()
17 .filter(condition)
18 .order_by_desc(extension::Column::TotalDownloadCount)
19 .order_by_asc(extension::Column::Name)
20 .limit(Some(limit as u64))
21 .filter(
22 extension::Column::LatestVersion
23 .into_expr()
24 .eq(extension_version::Column::Version.into_expr()),
25 )
26 .inner_join(extension_version::Entity)
27 .select_also(extension_version::Entity)
28 .all(&*tx)
29 .await?;
30
31 Ok(extensions
32 .into_iter()
33 .filter_map(|(extension, latest_version)| {
34 let version = latest_version?;
35 Some(ExtensionMetadata {
36 id: extension.external_id,
37 name: extension.name,
38 version: version.version,
39 authors: version
40 .authors
41 .split(',')
42 .map(|author| author.trim().to_string())
43 .collect::<Vec<_>>(),
44 description: version.description,
45 repository: version.repository,
46 published_at: version.published_at,
47 download_count: extension.total_download_count as u64,
48 })
49 })
50 .collect())
51 })
52 .await
53 }
54
55 pub async fn get_extension(&self, extension_id: &str) -> Result<Option<ExtensionMetadata>> {
56 self.transaction(|tx| async move {
57 let extension = extension::Entity::find()
58 .filter(extension::Column::ExternalId.eq(extension_id))
59 .filter(
60 extension::Column::LatestVersion
61 .into_expr()
62 .eq(extension_version::Column::Version.into_expr()),
63 )
64 .inner_join(extension_version::Entity)
65 .select_also(extension_version::Entity)
66 .one(&*tx)
67 .await?;
68
69 Ok(extension.and_then(|(extension, latest_version)| {
70 let version = latest_version?;
71 Some(ExtensionMetadata {
72 id: extension.external_id,
73 name: extension.name,
74 version: version.version,
75 authors: version
76 .authors
77 .split(',')
78 .map(|author| author.trim().to_string())
79 .collect::<Vec<_>>(),
80 description: version.description,
81 repository: version.repository,
82 published_at: version.published_at,
83 download_count: extension.total_download_count as u64,
84 })
85 }))
86 })
87 .await
88 }
89
90 pub async fn get_known_extension_versions<'a>(&self) -> Result<HashMap<String, Vec<String>>> {
91 self.transaction(|tx| async move {
92 let mut extension_external_ids_by_id = HashMap::default();
93
94 let mut rows = extension::Entity::find().stream(&*tx).await?;
95 while let Some(row) = rows.next().await {
96 let row = row?;
97 extension_external_ids_by_id.insert(row.id, row.external_id);
98 }
99 drop(rows);
100
101 let mut known_versions_by_extension_id: HashMap<String, Vec<String>> =
102 HashMap::default();
103 let mut rows = extension_version::Entity::find().stream(&*tx).await?;
104 while let Some(row) = rows.next().await {
105 let row = row?;
106
107 let Some(extension_id) = extension_external_ids_by_id.get(&row.extension_id) else {
108 continue;
109 };
110
111 let versions = known_versions_by_extension_id
112 .entry(extension_id.clone())
113 .or_default();
114 if let Err(ix) = versions.binary_search(&row.version) {
115 versions.insert(ix, row.version);
116 }
117 }
118 drop(rows);
119
120 Ok(known_versions_by_extension_id)
121 })
122 .await
123 }
124
125 pub async fn insert_extension_versions(
126 &self,
127 versions_by_extension_id: &HashMap<&str, Vec<NewExtensionVersion>>,
128 ) -> Result<()> {
129 self.transaction(|tx| async move {
130 for (external_id, versions) in versions_by_extension_id {
131 if versions.is_empty() {
132 continue;
133 }
134
135 let latest_version = versions
136 .iter()
137 .max_by_key(|version| &version.version)
138 .unwrap();
139
140 let insert = extension::Entity::insert(extension::ActiveModel {
141 name: ActiveValue::Set(latest_version.name.clone()),
142 external_id: ActiveValue::Set(external_id.to_string()),
143 id: ActiveValue::NotSet,
144 latest_version: ActiveValue::Set(latest_version.version.to_string()),
145 total_download_count: ActiveValue::NotSet,
146 })
147 .on_conflict(
148 OnConflict::columns([extension::Column::ExternalId])
149 .update_column(extension::Column::ExternalId)
150 .to_owned(),
151 );
152
153 let extension = if tx.support_returning() {
154 insert.exec_with_returning(&*tx).await?
155 } else {
156 // Sqlite
157 insert.exec_without_returning(&*tx).await?;
158 extension::Entity::find()
159 .filter(extension::Column::ExternalId.eq(*external_id))
160 .one(&*tx)
161 .await?
162 .ok_or_else(|| anyhow!("failed to insert extension"))?
163 };
164
165 extension_version::Entity::insert_many(versions.iter().map(|version| {
166 extension_version::ActiveModel {
167 extension_id: ActiveValue::Set(extension.id),
168 published_at: ActiveValue::Set(version.published_at),
169 version: ActiveValue::Set(version.version.to_string()),
170 authors: ActiveValue::Set(version.authors.join(", ")),
171 repository: ActiveValue::Set(version.repository.clone()),
172 description: ActiveValue::Set(version.description.clone()),
173 download_count: ActiveValue::NotSet,
174 }
175 }))
176 .on_conflict(OnConflict::new().do_nothing().to_owned())
177 .exec_without_returning(&*tx)
178 .await?;
179
180 if let Ok(db_version) = semver::Version::parse(&extension.latest_version) {
181 if db_version >= latest_version.version {
182 continue;
183 }
184 }
185
186 let mut extension = extension.into_active_model();
187 extension.latest_version = ActiveValue::Set(latest_version.version.to_string());
188 extension.name = ActiveValue::set(latest_version.name.clone());
189 extension::Entity::update(extension).exec(&*tx).await?;
190 }
191
192 Ok(())
193 })
194 .await
195 }
196
197 pub async fn record_extension_download(&self, extension: &str, version: &str) -> Result<bool> {
198 self.transaction(|tx| async move {
199 #[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)]
200 enum QueryId {
201 Id,
202 }
203
204 let extension_id: Option<ExtensionId> = extension::Entity::find()
205 .filter(extension::Column::ExternalId.eq(extension))
206 .select_only()
207 .column(extension::Column::Id)
208 .into_values::<_, QueryId>()
209 .one(&*tx)
210 .await?;
211 let Some(extension_id) = extension_id else {
212 return Ok(false);
213 };
214
215 extension_version::Entity::update_many()
216 .col_expr(
217 extension_version::Column::DownloadCount,
218 extension_version::Column::DownloadCount.into_expr().add(1),
219 )
220 .filter(
221 extension_version::Column::ExtensionId
222 .eq(extension_id)
223 .and(extension_version::Column::Version.eq(version)),
224 )
225 .exec(&*tx)
226 .await?;
227
228 extension::Entity::update_many()
229 .col_expr(
230 extension::Column::TotalDownloadCount,
231 extension::Column::TotalDownloadCount.into_expr().add(1),
232 )
233 .filter(extension::Column::Id.eq(extension_id))
234 .exec(&*tx)
235 .await?;
236
237 Ok(true)
238 })
239 .await
240 }
241}