extensions.rs

  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}