1mod operations;
2pub mod test_utils;
3
4use std::cmp::max;
5use std::collections::{BTreeMap, HashMap};
6use std::path::Path;
7
8use chrono::{DateTime, Utc};
9use kellnr_common::crate_data::{CrateData, CrateRegistryDep, CrateVersionData};
10use kellnr_common::crate_dependents::{CrateDependent, CrateDependents};
11use kellnr_common::crate_overview::CrateOverview;
12use kellnr_common::cratesio_prefetch_msg::{CratesioPrefetchMsg, UpdateData};
13use kellnr_common::index_metadata::{IndexDep, IndexMetadata};
14use kellnr_common::normalized_name::NormalizedName;
15use kellnr_common::original_name::OriginalName;
16use kellnr_common::prefetch::Prefetch;
17use kellnr_common::publish_metadata::PublishMetadata;
18use kellnr_common::version::{Version, latest_version};
19use kellnr_common::webhook::{Webhook, WebhookEvent, WebhookQueue};
20use kellnr_entity::prelude::*;
21use kellnr_entity::{
22 auth_token, crate_author, crate_author_to_crate, crate_category, crate_category_to_crate,
23 crate_dependency, crate_group, crate_index, crate_keyword, crate_keyword_to_crate, crate_meta,
24 crate_user, cratesio_crate, cratesio_index, cratesio_meta, doc_queue, group, group_user, krate,
25 oauth2_identity, oauth2_state, owner, session, toolchain, toolchain_component,
26 toolchain_target, user, webhook, webhook_queue,
27};
28use kellnr_migration::iden::{
29 AuthTokenIden, CrateIden, CrateMetaIden, CratesIoIden, CratesIoMetaIden, GroupIden,
30};
31use kellnr_settings::constants::OAUTH2_STATE_MAX_AGE;
32use sea_orm::entity::prelude::Uuid;
33use sea_orm::prelude::async_trait::async_trait;
34use sea_orm::query::{QueryOrder, QuerySelect, TransactionTrait};
35use sea_orm::sea_query::{
36 Alias, Cond, Condition, Expr, Func, Iden, JoinType, LikeExpr, Order, Query, SelectStatement,
37 UnionType,
38};
39use sea_orm::{
40 ActiveModelTrait, ActiveValue, ColumnTrait, DatabaseConnection, EntityTrait, ExprTrait,
41 FromQueryResult, ModelTrait, QueryFilter, RelationTrait, Set, Statement,
42};
43use tracing::warn;
44
45use crate::error::DbError;
46use crate::password::{PwdVerification, hash_pwd, hash_token, verify_pwd};
47use crate::provider::{
48 ChannelInfo, CrateSort, CrateTagFilter, CrateVisibility, CratesioVersionMeta, DbResult,
49 OAuth2StateData, PrefetchState, SessionInfo, ToolchainComponentInfo, ToolchainTargetInfo,
50 ToolchainWithTargets,
51};
52use crate::tables::init_database;
53use crate::{
54 AuthToken, ConString, CrateMeta, CrateSummary, DbProvider, DocQueueEntry, Group, User,
55};
56
57const DB_DATE_FORMAT: &str = "%Y-%m-%d %H:%M:%S";
58
59const TOKEN_LAST_USED_INTERVAL: chrono::TimeDelta = chrono::TimeDelta::hours(1);
61
62fn oauth2_state_expiry_cutoff() -> String {
64 let max_age =
65 chrono::Duration::from_std(OAUTH2_STATE_MAX_AGE).expect("constant fits chrono::Duration");
66 (Utc::now() - max_age).format(DB_DATE_FORMAT).to_string()
67}
68
69fn escape_like_pattern(input: &str) -> String {
74 input
75 .replace('\\', "\\\\")
76 .replace('%', "\\%")
77 .replace('_', "\\_")
78}
79
80const MAX_SESSION_AGE: std::time::Duration = std::time::Duration::from_hours(876_000);
84
85fn session_age_to_duration(d: std::time::Duration) -> Option<chrono::Duration> {
90 if d.is_zero() {
91 return None;
92 }
93 let clamped = d.min(MAX_SESSION_AGE);
94 Some(chrono::Duration::from_std(clamped).expect("clamped session age fits chrono::Duration"))
97}
98
99#[derive(Debug, PartialEq, FromQueryResult)]
100struct CountResult {
101 count: Option<i64>,
102}
103
104pub struct Database {
105 db_con: DatabaseConnection,
106 session_age: Option<chrono::Duration>,
109}
110
111impl Database {
112 pub async fn new(con: &ConString, max_con: u32) -> Result<Self, DbError> {
113 let db_con = init_database(con, max_con)
114 .await
115 .map_err(|e| DbError::InitializationError(e.to_string()))?;
116
117 if operations::no_user_exists(&db_con).await? {
118 operations::insert_admin_credentials(&db_con, con).await?;
119 }
120
121 Ok(Self {
122 db_con,
123 session_age: session_age_to_duration(con.session_age()),
124 })
125 }
126
127 fn session_expiry_cutoff(&self) -> Option<String> {
131 self.session_age
132 .map(|age| (Utc::now() - age).format(DB_DATE_FORMAT).to_string())
133 }
134
135 async fn upgrade_legacy_pwd_hash(&self, user: &User, pwd: &str) -> DbResult<()> {
139 let hashed = hash_pwd(pwd).await;
140 user::Entity::update_many()
141 .col_expr(user::Column::Pwd, Expr::value(hashed))
142 .col_expr(user::Column::Salt, Expr::value(String::new()))
143 .filter(user::Column::Id.eq(i64::from(user.id)))
144 .filter(user::Column::Pwd.eq(user.pwd.as_str()))
145 .exec(&self.db_con)
146 .await?;
147 Ok(())
148 }
149
150 async fn get_user_model(&self, name: &str) -> DbResult<user::Model> {
152 user::Entity::find()
153 .filter(user::Column::Name.eq(name))
154 .one(&self.db_con)
155 .await?
156 .ok_or_else(|| DbError::UserNotFound(name.to_owned()))
157 }
158
159 async fn get_krate_model(&self, crate_name: &NormalizedName) -> DbResult<krate::Model> {
161 krate::Entity::find()
162 .filter(krate::Column::Name.eq(crate_name))
163 .one(&self.db_con)
164 .await?
165 .ok_or_else(|| DbError::CrateNotFound(crate_name.to_string()))
166 }
167
168 async fn get_group_model(&self, name: &str) -> DbResult<group::Model> {
170 group::Entity::find()
171 .filter(group::Column::Name.eq(name))
172 .one(&self.db_con)
173 .await?
174 .ok_or_else(|| DbError::GroupNotFound(name.to_owned()))
175 }
176
177 async fn toolchain_with_targets(&self, tc: toolchain::Model) -> DbResult<ToolchainWithTargets> {
179 let targets = toolchain_target::Entity::find()
180 .filter(toolchain_target::Column::ToolchainFk.eq(tc.id))
181 .all(&self.db_con)
182 .await?;
183
184 let mut target_infos = Vec::with_capacity(targets.len());
185 for t in targets {
186 let components = toolchain_component::Entity::find()
187 .filter(toolchain_component::Column::ToolchainTargetFk.eq(t.id))
188 .all(&self.db_con)
189 .await?;
190 target_infos.push(ToolchainTargetInfo {
191 id: t.id,
192 target: t.target,
193 storage_path: t.storage_path,
194 hash: t.hash,
195 size: t.size,
196 status: t.status,
197 components: components
198 .into_iter()
199 .map(ToolchainComponentInfo::from)
200 .collect(),
201 });
202 }
203
204 Ok(ToolchainWithTargets {
205 id: tc.id,
206 name: tc.name,
207 version: tc.version,
208 date: tc.date,
209 channel: tc.channel,
210 created: tc.created,
211 targets: target_infos,
212 })
213 }
214
215 async fn get_crate_index_model(
217 &self,
218 crate_name: &NormalizedName,
219 version: &Version,
220 ) -> DbResult<crate_index::Model> {
221 crate_index::Entity::find()
222 .filter(crate_index::Column::Name.eq(crate_name))
223 .filter(crate_index::Column::Vers.eq(version))
224 .one(&self.db_con)
225 .await?
226 .ok_or_else(|| DbError::CrateIndexNotFound(crate_name.to_string(), version.to_string()))
227 }
228
229 async fn get_toolchain_by_name_version(
231 &self,
232 name: &str,
233 version: &str,
234 ) -> DbResult<Option<toolchain::Model>> {
235 toolchain::Entity::find()
236 .filter(toolchain::Column::Name.eq(name))
237 .filter(toolchain::Column::Version.eq(version))
238 .one(&self.db_con)
239 .await
240 .map_err(Into::into)
241 }
242
243 async fn get_crate_group_by_name_and_group(
245 &self,
246 crate_name: &NormalizedName,
247 group: &str,
248 ) -> DbResult<Option<crate_group::Model>> {
249 crate_group::Entity::find()
250 .join(JoinType::InnerJoin, crate_group::Relation::Krate.def())
251 .join(JoinType::InnerJoin, crate_group::Relation::Group.def())
252 .filter(
253 Cond::all()
254 .add(krate::Column::Name.eq(crate_name))
255 .add(group::Column::Name.eq(group)),
256 )
257 .one(&self.db_con)
258 .await
259 .map_err(Into::into)
260 }
261
262 async fn get_group_user_by_group_and_user(
264 &self,
265 group_name: &str,
266 user: &str,
267 ) -> DbResult<Option<group_user::Model>> {
268 group_user::Entity::find()
269 .join(JoinType::InnerJoin, group_user::Relation::Group.def())
270 .join(JoinType::InnerJoin, group_user::Relation::User.def())
271 .filter(
272 Cond::all()
273 .add(group::Column::Name.eq(group_name))
274 .add(user::Column::Name.eq(user)),
275 )
276 .one(&self.db_con)
277 .await
278 .map_err(Into::into)
279 }
280
281 async fn get_crate_user_by_crate_and_user(
283 &self,
284 crate_name: &NormalizedName,
285 user: &str,
286 ) -> DbResult<Option<crate_user::Model>> {
287 crate_user::Entity::find()
288 .join(JoinType::InnerJoin, crate_user::Relation::Krate.def())
289 .join(JoinType::InnerJoin, crate_user::Relation::User.def())
290 .filter(
291 Cond::all()
292 .add(krate::Column::Name.eq(crate_name))
293 .add(user::Column::Name.eq(user)),
294 )
295 .one(&self.db_con)
296 .await
297 .map_err(Into::into)
298 }
299
300 fn add_filter_by_name_and_description(
311 mut query: SelectStatement,
312 contains: Option<&str>,
313 name: Expr,
314 description: Expr,
315 ) -> SelectStatement {
316 if let Some(contains) = contains {
317 let pattern = format!("%{}%", escape_like_pattern(contains)).to_lowercase();
318 query.and_where(
319 Func::lower(name)
320 .like(LikeExpr::new(&pattern).escape('\\'))
321 .or(Func::lower(description).like(LikeExpr::new(&pattern).escape('\\'))),
322 );
323 }
324 query
325 }
326
327 fn search_rank(contains: Option<&str>, name: &Expr) -> Expr {
331 let Some(contains) = contains else {
332 return Expr::val(0);
333 };
334 let query = contains.to_lowercase();
335 let escaped = escape_like_pattern(contains).to_lowercase();
336 let lower_name = || Func::lower(name.clone());
337 Expr::case(lower_name().eq(query), 0)
338 .case(
339 lower_name().like(LikeExpr::new(format!("{escaped}%")).escape('\\')),
340 1,
341 )
342 .case(
343 lower_name().like(LikeExpr::new(format!("%{escaped}%")).escape('\\')),
344 2,
345 )
346 .finally(3)
347 .into()
348 }
349
350 fn select_from_crates(mut query: SelectStatement, contains: Option<&str>) -> SelectStatement {
353 query
354 .inner_join(
355 CrateIden::Table,
356 Expr::col((CrateMetaIden::Table, CrateMetaIden::CrateFk))
357 .equals((CrateIden::Table, CrateIden::Id)),
358 )
359 .and_where(
360 Expr::col((CrateMetaIden::Table, CrateMetaIden::Version))
361 .equals((CrateIden::Table, CrateIden::MaxVersion)),
362 );
363
364 Self::add_filter_by_name_and_description(
365 query,
366 contains,
367 Expr::col((CrateIden::Table, CrateIden::OriginalName)),
368 Expr::col((CrateIden::Table, CrateIden::Description)),
369 )
370 }
371
372 fn select_from_cache(mut query: SelectStatement, contains: Option<&str>) -> SelectStatement {
376 query
377 .inner_join(
378 CratesIoIden::Table,
379 Expr::col((CratesIoMetaIden::Table, CratesIoMetaIden::CratesIoFk))
380 .equals((CratesIoIden::Table, CratesIoIden::Id)),
381 )
382 .and_where(
383 Expr::col((CratesIoMetaIden::Table, CratesIoMetaIden::Version))
384 .equals((CratesIoIden::Table, CratesIoIden::MaxVersion)),
385 );
386
387 Self::add_filter_by_name_and_description(
388 query,
389 contains,
390 Expr::col((CratesIoIden::Table, CratesIoIden::OriginalName)),
391 Expr::col((CratesIoIden::Table, CratesIoIden::Description)),
392 )
393 }
394
395 fn crate_visibility_condition(visibility: &CrateVisibility) -> Option<Condition> {
398 let unrestricted = Expr::col((CrateIden::Table, CrateIden::RestrictedDownload)).eq(false);
399 match visibility {
400 CrateVisibility::All => None,
401 CrateVisibility::Unrestricted => Some(Cond::all().add(unrestricted)),
402 CrateVisibility::User(user) => {
403 let crate_id = || Expr::col((CrateIden::Table, CrateIden::Id));
404 Some(
405 Cond::any()
406 .add(unrestricted)
407 .add(crate_id().in_subquery(Self::owned_crate_ids(user)))
408 .add(crate_id().in_subquery(Self::crate_user_crate_ids(user)))
409 .add(crate_id().in_subquery(Self::crate_group_crate_ids(user))),
410 )
411 }
412 }
413 }
414
415 fn crate_tag_condition(tags: &CrateTagFilter) -> Option<Condition> {
418 if tags.is_empty() {
419 return None;
420 }
421 let crate_id = || Expr::col((CrateIden::Table, CrateIden::Id));
422 let mut condition = Cond::all();
423 if let Some(keyword) = &tags.keyword {
424 condition = condition.add(crate_id().in_subquery(Self::keyword_crate_ids(keyword)));
425 }
426 if let Some(category) = &tags.category {
427 condition = condition.add(crate_id().in_subquery(Self::category_crate_ids(category)));
428 }
429 Some(condition)
430 }
431
432 fn keyword_crate_ids(keyword: &str) -> SelectStatement {
433 Query::select()
434 .column((
435 crate_keyword_to_crate::Entity,
436 crate_keyword_to_crate::Column::CrateFk,
437 ))
438 .from(crate_keyword_to_crate::Entity)
439 .inner_join(
440 crate_keyword::Entity,
441 Expr::col((crate_keyword::Entity, crate_keyword::Column::Id)).equals((
442 crate_keyword_to_crate::Entity,
443 crate_keyword_to_crate::Column::KeywordFk,
444 )),
445 )
446 .and_where(
447 Func::lower(Expr::col((
448 crate_keyword::Entity,
449 crate_keyword::Column::Keyword,
450 )))
451 .eq(keyword.to_lowercase()),
452 )
453 .to_owned()
454 }
455
456 fn category_crate_ids(category: &str) -> SelectStatement {
457 Query::select()
458 .column((
459 crate_category_to_crate::Entity,
460 crate_category_to_crate::Column::CrateFk,
461 ))
462 .from(crate_category_to_crate::Entity)
463 .inner_join(
464 crate_category::Entity,
465 Expr::col((crate_category::Entity, crate_category::Column::Id)).equals((
466 crate_category_to_crate::Entity,
467 crate_category_to_crate::Column::CategoryFk,
468 )),
469 )
470 .and_where(
471 Func::lower(Expr::col((
472 crate_category::Entity,
473 crate_category::Column::Category,
474 )))
475 .eq(category.to_lowercase()),
476 )
477 .to_owned()
478 }
479
480 fn user_id_by_name(user: &str) -> SelectStatement {
481 Query::select()
482 .column((user::Entity, user::Column::Id))
483 .from(user::Entity)
484 .and_where(Expr::col((user::Entity, user::Column::Name)).eq(user))
485 .to_owned()
486 }
487
488 fn owned_crate_ids(user: &str) -> SelectStatement {
489 Query::select()
490 .column((owner::Entity, owner::Column::CrateFk))
491 .from(owner::Entity)
492 .and_where(
493 Expr::col((owner::Entity, owner::Column::UserFk))
494 .in_subquery(Self::user_id_by_name(user)),
495 )
496 .to_owned()
497 }
498
499 fn crate_user_crate_ids(user: &str) -> SelectStatement {
500 Query::select()
501 .column((crate_user::Entity, crate_user::Column::CrateFk))
502 .from(crate_user::Entity)
503 .and_where(
504 Expr::col((crate_user::Entity, crate_user::Column::UserFk))
505 .in_subquery(Self::user_id_by_name(user)),
506 )
507 .to_owned()
508 }
509
510 fn crate_group_crate_ids(user: &str) -> SelectStatement {
511 Query::select()
512 .column((crate_group::Entity, crate_group::Column::CrateFk))
513 .from(crate_group::Entity)
514 .inner_join(
515 group_user::Entity,
516 Expr::col((group_user::Entity, group_user::Column::GroupFk))
517 .equals((crate_group::Entity, crate_group::Column::GroupFk)),
518 )
519 .and_where(
520 Expr::col((group_user::Entity, group_user::Column::UserFk))
521 .in_subquery(Self::user_id_by_name(user)),
522 )
523 .to_owned()
524 }
525
526 async fn query_crates(
529 &self,
530 contains: Option<&str>,
531 limit_offset: Option<(u64, u64)>,
532 cache: bool,
533 visibility: &CrateVisibility,
534 sort: CrateSort,
535 tags: &CrateTagFilter,
536 ) -> DbResult<Vec<CrateOverview>> {
537 fn select_fields(fields: &[(Expr, &str)]) -> SelectStatement {
539 let mut query = Query::select();
540 for (col, alias) in fields {
541 query.expr_as(col.clone(), Alias::new(*alias));
542 }
543 query
544 }
545
546 let mut query = select_fields(&[
547 (Expr::col(CrateIden::OriginalName), "name"),
548 (Expr::col(CrateIden::MaxVersion), "version"),
549 (Expr::col(CrateIden::LastUpdated), "date"),
550 (Expr::col(CrateIden::TotalDownloads), "total_downloads"),
551 (Expr::col(CrateIden::Description), "description"),
552 (Expr::col(CrateMetaIden::Documentation), "documentation"),
553 (Expr::col(CrateIden::Repository), "repository"),
554 (Expr::cust("false"), "is_cache"),
555 (
556 Self::search_rank(
557 contains,
558 &Expr::col((CrateIden::Table, CrateIden::OriginalName)),
559 ),
560 "search_rank",
561 ),
562 ]);
563 query.from(CrateMetaIden::Table);
564 query = Self::select_from_crates(query, contains);
565 if let Some(condition) = Self::crate_visibility_condition(visibility) {
566 query.cond_where(condition);
567 }
568 if let Some(condition) = Self::crate_tag_condition(tags) {
569 query.cond_where(condition);
570 }
571
572 if cache && tags.is_empty() {
574 let mut query2 = select_fields(&[
575 (Expr::col(CratesIoIden::OriginalName), "name"),
576 (Expr::col(CratesIoIden::MaxVersion), "version"),
577 (Expr::col(CratesIoIden::LastModified), "date"),
578 (Expr::col(CratesIoIden::TotalDownloads), "total_downloads"),
579 (Expr::col(CratesIoIden::Description), "description"),
580 (Expr::col(CratesIoMetaIden::Documentation), "documentation"),
581 (Expr::cust("NULL"), "repository"),
583 (Expr::cust("true"), "is_cache"),
584 (
585 Self::search_rank(
586 contains,
587 &Expr::col((CratesIoIden::Table, CratesIoIden::OriginalName)),
588 ),
589 "search_rank",
590 ),
591 ]);
592 query2.from(CratesIoMetaIden::Table);
593 query2 = Self::select_from_cache(query2, contains);
594
595 query.union(UnionType::All, query2);
596 }
597
598 match sort {
601 CrateSort::Name => query.order_by(Alias::new("search_rank"), Order::Asc),
602 CrateSort::Updated => query.order_by(Alias::new("date"), Order::Desc),
604 CrateSort::Downloads => query.order_by(Alias::new("total_downloads"), Order::Desc),
605 };
606 query.order_by(Alias::new("name"), Order::Asc);
607 if let Some((limit, offset)) = limit_offset {
608 query.limit(limit).offset(offset);
609 }
610
611 let stmt = self.db_con.get_database_backend().build(&query);
612
613 CrateOverview::find_by_statement(stmt)
614 .all(&self.db_con)
615 .await
616 .map_err(DbError::from)
617 }
618
619 async fn count_crates(
620 &self,
621 contains: Option<&str>,
622 cache: bool,
623 visibility: &CrateVisibility,
624 tags: &CrateTagFilter,
625 ) -> DbResult<u64> {
626 let mut query = Query::select();
627 query.expr_as(
628 Expr::col((CrateIden::Table, CrateIden::Id)).count(),
629 Alias::new("count"),
630 );
631 query.from(CrateMetaIden::Table);
632 query = Self::select_from_crates(query, contains);
633 if let Some(condition) = Self::crate_visibility_condition(visibility) {
634 query.cond_where(condition);
635 }
636 if let Some(condition) = Self::crate_tag_condition(tags) {
637 query.cond_where(condition);
638 }
639
640 let count = self
641 .count_by_statement(
642 self.db_con.get_database_backend().build(&query),
643 DbError::FailedToCountCrates,
644 )
645 .await?;
646
647 let cache_count = if cache && tags.is_empty() {
648 let mut query2 = Query::select();
649 query2.expr_as(
650 Expr::col((CratesIoIden::Table, CratesIoIden::Id)).count(),
651 Alias::new("count"),
652 );
653 query2.from(CratesIoMetaIden::Table);
654 query2 = Self::select_from_cache(query2, contains);
655
656 self.count_by_statement(
657 self.db_con.get_database_backend().build(&query2),
658 DbError::FailedToCountCrates,
659 )
660 .await?
661 } else {
662 0
663 };
664
665 Ok(count + cache_count)
666 }
667
668 async fn count<T>(&self, table: T, id_column: T, error: DbError) -> DbResult<u64>
670 where
671 T: Iden + Copy,
672 {
673 let statement = self.db_con.get_database_backend().build(
674 Query::select()
675 .expr_as(Expr::col((table, id_column)).count(), Alias::new("count"))
676 .from(table),
677 );
678 self.count_by_statement(statement, error).await
679 }
680
681 async fn count_by_statement(&self, statement: Statement, error: DbError) -> DbResult<u64> {
683 let Some(result) = CountResult::find_by_statement(statement)
684 .one(&self.db_con)
685 .await?
686 else {
687 return Err(error);
688 };
689 result
690 .count
691 .and_then(|v| u64::try_from(v).ok())
692 .ok_or(error)
693 }
694
695 async fn get_webhook_model(&self, id: &str) -> DbResult<webhook::Model> {
696 webhook::Entity::find()
697 .filter(webhook::Column::Id.eq(
698 TryInto::<Uuid>::try_into(id).map_err(|_| DbError::InvalidId(id.to_string()))?,
699 ))
700 .one(&self.db_con)
701 .await?
702 .ok_or(DbError::WebhookNotFound)
703 }
704
705 async fn get_webhook_queue_model(&self, id: &str) -> DbResult<webhook_queue::Model> {
706 webhook_queue::Entity::find()
707 .filter(webhook_queue::Column::Id.eq(
708 TryInto::<Uuid>::try_into(id).map_err(|_| DbError::InvalidId(id.to_string()))?,
709 ))
710 .one(&self.db_con)
711 .await?
712 .ok_or(DbError::WebhookNotFound)
713 }
714
715 async fn get_owner_by_crate_and_user(
716 &self,
717 crate_name: &str,
718 owner_name: &str,
719 ) -> DbResult<Option<owner::Model>> {
720 owner::Entity::find()
721 .join(JoinType::InnerJoin, owner::Relation::Krate.def())
722 .join(JoinType::InnerJoin, owner::Relation::User.def())
723 .filter(
724 Cond::all()
725 .add(krate::Column::Name.eq(crate_name))
726 .add(user::Column::Name.eq(owner_name)),
727 )
728 .one(&self.db_con)
729 .await
730 .map_err(Into::into)
731 }
732
733 async fn delete_or_not_found<M, A>(&self, model: Option<M>, err: DbError) -> DbResult<()>
734 where
735 M: ModelTrait + sea_orm::IntoActiveModel<A>,
736 A: ActiveModelTrait<Entity = M::Entity> + sea_orm::ActiveModelBehavior + Send,
737 {
738 model.ok_or(err)?.delete(&self.db_con).await?;
739 Ok(())
740 }
741}
742
743fn webhook_model_to_obj(w: webhook::Model) -> DbResult<Webhook> {
744 let event = w
745 .event
746 .as_str()
747 .try_into()
748 .map_err(|_| DbError::InvalidWebhookEvent(w.event))?;
749 Ok(Webhook {
750 id: Some(w.id.into()),
751 name: w.name,
752 event,
753 callback_url: w.callback_url,
754 secret: w.secret,
755 })
756}
757
758macro_rules! impl_add_relation {
767 ($method:ident, $relation_entity:ident, $insert_entity:ident, $fk1:ident, $fk2:ident) => {
768 async fn $method(&self, fk1: i64, fk2: i64) -> DbResult<()> {
769 let model = $relation_entity::ActiveModel {
770 $fk1: Set(fk1),
771 $fk2: Set(fk2),
772 ..Default::default()
773 };
774 $insert_entity::insert(model).exec(&self.db_con).await?;
775 Ok(())
776 }
777 };
778}
779
780impl Database {
781 impl_add_relation!(
782 add_crate_user_impl,
783 crate_user,
784 CrateUser,
785 crate_fk,
786 user_fk
787 );
788 impl_add_relation!(
789 add_crate_group_impl,
790 crate_group,
791 CrateGroup,
792 crate_fk,
793 group_fk
794 );
795 impl_add_relation!(
796 add_group_user_impl,
797 group_user,
798 GroupUser,
799 group_fk,
800 user_fk
801 );
802 impl_add_relation!(add_owner_impl, owner, Owner, crate_fk, user_fk);
803}
804
805#[async_trait]
806impl DbProvider for Database {
807 async fn get_total_unique_cached_crates(&self) -> DbResult<u64> {
808 self.count(
809 CratesIoIden::Table,
810 CratesIoIden::Id,
811 DbError::FailedToCountCrates,
812 )
813 .await
814 }
815
816 async fn get_total_cached_crate_versions(&self) -> DbResult<u64> {
817 self.count(
818 CratesIoMetaIden::Table,
819 CratesIoMetaIden::Id,
820 DbError::FailedToCountCrateVersions,
821 )
822 .await
823 }
824
825 async fn get_total_cached_downloads(&self) -> DbResult<u64> {
826 #[derive(FromQueryResult)]
827 struct Model {
828 total_downloads: i64,
829 }
830
831 let total_downloads = cratesio_crate::Entity::find()
832 .select_only()
833 .column(cratesio_crate::Column::TotalDownloads)
834 .into_model::<Model>()
835 .all(&self.db_con)
836 .await?;
837
838 Ok(total_downloads
839 .iter()
840 .map(|m| m.total_downloads as u64)
841 .sum())
842 }
843
844 async fn authenticate_user(&self, name: &str, pwd: &str) -> DbResult<User> {
845 let user = self.get_user(name).await?;
846
847 match verify_pwd(pwd, &user.pwd, &user.salt).await {
848 PwdVerification::Valid => Ok(user),
849 PwdVerification::ValidLegacy => {
850 if let Err(e) = self.upgrade_legacy_pwd_hash(&user, pwd).await {
854 warn!("Failed to upgrade password hash of user {}: {e}", user.name);
855 }
856 Ok(user)
857 }
858 PwdVerification::Invalid => Err(DbError::PasswordMismatch),
859 }
860 }
861
862 async fn increase_download_counter(
863 &self,
864 crate_name: &NormalizedName,
865 crate_version: &Version,
866 ) -> DbResult<()> {
867 let txn = self.db_con.begin().await?;
868
869 krate::Entity::update_many()
870 .col_expr(
871 krate::Column::TotalDownloads,
872 Expr::col(krate::Column::TotalDownloads).add(1),
873 )
874 .filter(krate::Column::Name.eq(crate_name))
875 .exec(&txn)
876 .await?;
877
878 let crate_id = krate::Entity::find()
879 .filter(krate::Column::Name.eq(crate_name))
880 .one(&txn)
881 .await?
882 .ok_or_else(|| DbError::CrateNotFound(crate_name.to_string()))?
883 .id;
884
885 crate_meta::Entity::update_many()
886 .col_expr(
887 crate_meta::Column::Downloads,
888 Expr::col(crate_meta::Column::Downloads).add(1),
889 )
890 .filter(
891 Cond::all()
892 .add(crate_meta::Column::Version.eq(crate_version))
893 .add(crate_meta::Column::CrateFk.eq(crate_id)),
894 )
895 .exec(&txn)
896 .await?;
897
898 txn.commit().await?;
899 Ok(())
900 }
901
902 async fn increase_cached_download_counter(
903 &self,
904 crate_name: &NormalizedName,
905 crate_version: &Version,
906 ) -> DbResult<()> {
907 let txn = self.db_con.begin().await?;
908
909 cratesio_crate::Entity::update_many()
910 .col_expr(
911 cratesio_crate::Column::TotalDownloads,
912 Expr::col(cratesio_crate::Column::TotalDownloads).add(1),
913 )
914 .filter(cratesio_crate::Column::Name.eq(crate_name))
915 .exec(&txn)
916 .await?;
917
918 let crate_id = cratesio_crate::Entity::find()
919 .filter(cratesio_crate::Column::Name.eq(crate_name))
920 .one(&txn)
921 .await?
922 .ok_or_else(|| DbError::CrateNotFound(crate_name.to_string()))?
923 .id;
924
925 cratesio_meta::Entity::update_many()
926 .col_expr(
927 cratesio_meta::Column::Downloads,
928 Expr::col(cratesio_meta::Column::Downloads).add(1),
929 )
930 .filter(
931 Cond::all()
932 .add(cratesio_meta::Column::Version.eq(crate_version))
933 .add(cratesio_meta::Column::CratesIoFk.eq(crate_id)),
934 )
935 .exec(&txn)
936 .await?;
937
938 txn.commit().await?;
939 Ok(())
940 }
941
942 async fn increase_download_counter_by(
943 &self,
944 crate_name: &NormalizedName,
945 crate_version: &Version,
946 count: u64,
947 ) -> DbResult<()> {
948 let txn = self.db_con.begin().await?;
949
950 krate::Entity::update_many()
951 .col_expr(
952 krate::Column::TotalDownloads,
953 Expr::col(krate::Column::TotalDownloads).add(count as i64),
954 )
955 .filter(krate::Column::Name.eq(crate_name))
956 .exec(&txn)
957 .await?;
958
959 let crate_id = krate::Entity::find()
960 .filter(krate::Column::Name.eq(crate_name))
961 .one(&txn)
962 .await?
963 .ok_or_else(|| DbError::CrateNotFound(crate_name.to_string()))?
964 .id;
965
966 crate_meta::Entity::update_many()
967 .col_expr(
968 crate_meta::Column::Downloads,
969 Expr::col(crate_meta::Column::Downloads).add(count as i64),
970 )
971 .filter(
972 Cond::all()
973 .add(crate_meta::Column::Version.eq(crate_version))
974 .add(crate_meta::Column::CrateFk.eq(crate_id)),
975 )
976 .exec(&txn)
977 .await?;
978
979 txn.commit().await?;
980 Ok(())
981 }
982
983 async fn increase_cached_download_counter_by(
984 &self,
985 crate_name: &NormalizedName,
986 crate_version: &Version,
987 count: u64,
988 ) -> DbResult<()> {
989 let txn = self.db_con.begin().await?;
990
991 cratesio_crate::Entity::update_many()
992 .col_expr(
993 cratesio_crate::Column::TotalDownloads,
994 Expr::col(cratesio_crate::Column::TotalDownloads).add(count as i64),
995 )
996 .filter(cratesio_crate::Column::Name.eq(crate_name))
997 .exec(&txn)
998 .await?;
999
1000 let crate_id = cratesio_crate::Entity::find()
1001 .filter(cratesio_crate::Column::Name.eq(crate_name))
1002 .one(&txn)
1003 .await?
1004 .ok_or_else(|| DbError::CrateNotFound(crate_name.to_string()))?
1005 .id;
1006
1007 cratesio_meta::Entity::update_many()
1008 .col_expr(
1009 cratesio_meta::Column::Downloads,
1010 Expr::col(cratesio_meta::Column::Downloads).add(count as i64),
1011 )
1012 .filter(
1013 Cond::all()
1014 .add(cratesio_meta::Column::Version.eq(crate_version))
1015 .add(cratesio_meta::Column::CratesIoFk.eq(crate_id)),
1016 )
1017 .exec(&txn)
1018 .await?;
1019
1020 txn.commit().await?;
1021 Ok(())
1022 }
1023
1024 async fn get_last_updated_crate(
1025 &self,
1026 visibility: &CrateVisibility,
1027 ) -> DbResult<Option<(OriginalName, Version)>> {
1028 let mut query = krate::Entity::find();
1029 if let Some(condition) = Self::crate_visibility_condition(visibility) {
1030 query = query.filter(condition);
1031 }
1032 let krate = query
1033 .order_by_desc(krate::Column::LastUpdated)
1034 .one(&self.db_con)
1035 .await?;
1036
1037 if let Some(krate) = krate {
1038 let name = OriginalName::from_unchecked(krate.original_name);
1040 let version = Version::from_unchecked_str(&krate.max_version);
1042 Ok(Some((name, version)))
1043 } else {
1044 Ok(None)
1045 }
1046 }
1047
1048 async fn validate_session(&self, session_token: &str) -> DbResult<SessionInfo> {
1049 let mut query = user::Entity::find()
1053 .join(JoinType::InnerJoin, user::Relation::Session.def())
1054 .filter(session::Column::Token.eq(hash_token(session_token)));
1055 if let Some(cutoff) = self.session_expiry_cutoff() {
1056 query = query.filter(session::Column::Created.gte(cutoff));
1057 }
1058
1059 let u = query
1060 .one(&self.db_con)
1061 .await?
1062 .ok_or(DbError::SessionNotFound)?;
1063
1064 Ok(SessionInfo {
1065 name: u.name,
1066 is_admin: u.is_admin,
1067 is_read_only: u.is_read_only,
1068 })
1069 }
1070
1071 async fn delete_expired_sessions(&self) -> DbResult<u64> {
1072 let Some(cutoff) = self.session_expiry_cutoff() else {
1074 return Ok(0);
1075 };
1076 let res = session::Entity::delete_many()
1077 .filter(session::Column::Created.lt(cutoff))
1078 .exec(&self.db_con)
1079 .await?;
1080 Ok(res.rows_affected)
1081 }
1082
1083 async fn add_session_token(&self, name: &str, session_token: &str) -> DbResult<()> {
1084 let user = self.get_user(name).await?;
1085 let created = Utc::now().format(DB_DATE_FORMAT).to_string();
1086
1087 let s = session::ActiveModel {
1088 token: Set(hash_token(session_token)),
1089 created: Set(created),
1090 user_fk: Set(user.id as i64),
1091 ..Default::default()
1092 };
1093
1094 s.insert(&self.db_con).await?;
1095 Ok(())
1096 }
1097
1098 async fn delete_user_sessions(&self, user_name: &str) -> DbResult<u64> {
1099 let user = self.get_user_model(user_name).await?;
1100 let res = session::Entity::delete_many()
1101 .filter(session::Column::UserFk.eq(user.id))
1102 .exec(&self.db_con)
1103 .await?;
1104 Ok(res.rows_affected)
1105 }
1106
1107 async fn delete_other_user_sessions(
1108 &self,
1109 user_name: &str,
1110 keep_session_token: &str,
1111 ) -> DbResult<u64> {
1112 let user = self.get_user_model(user_name).await?;
1113 let res = session::Entity::delete_many()
1114 .filter(session::Column::UserFk.eq(user.id))
1115 .filter(session::Column::Token.ne(hash_token(keep_session_token)))
1116 .exec(&self.db_con)
1117 .await?;
1118 Ok(res.rows_affected)
1119 }
1120
1121 async fn add_crate_user(&self, crate_name: &NormalizedName, user: &str) -> DbResult<()> {
1122 let crate_fk = self.get_krate_model(crate_name).await?.id;
1123 let user_fk = self.get_user_model(user).await?.id;
1124 self.add_crate_user_impl(crate_fk, user_fk).await
1125 }
1126
1127 async fn add_crate_group(&self, crate_name: &NormalizedName, group: &str) -> DbResult<()> {
1128 let crate_fk = self.get_krate_model(crate_name).await?.id;
1129 let group_fk = self.get_group_model(group).await?.id;
1130 self.add_crate_group_impl(crate_fk, group_fk).await
1131 }
1132
1133 async fn add_group_user(&self, group_name: &str, user: &str) -> DbResult<()> {
1134 let group_fk = self.get_group_model(group_name).await?.id;
1135 let user_fk = self.get_user_model(user).await?.id;
1136 self.add_group_user_impl(group_fk, user_fk).await
1137 }
1138
1139 async fn add_owner(&self, crate_name: &NormalizedName, owner: &str) -> DbResult<()> {
1140 let crate_fk = self.get_krate_model(crate_name).await?.id;
1141 let user_fk = self.get_user_model(owner).await?.id;
1142 self.add_owner_impl(crate_fk, user_fk).await
1143 }
1144
1145 async fn is_download_restricted(&self, crate_name: &NormalizedName) -> DbResult<bool> {
1146 Ok(krate::Entity::find()
1147 .filter(krate::Column::Name.eq(crate_name))
1148 .one(&self.db_con)
1149 .await?
1150 .is_some_and(|model| model.restricted_download))
1151 }
1152
1153 async fn change_download_restricted(
1154 &self,
1155 crate_name: &NormalizedName,
1156 restricted: bool,
1157 ) -> DbResult<()> {
1158 let mut krate: krate::ActiveModel = self.get_krate_model(crate_name).await?.into();
1159 krate.restricted_download = Set(restricted);
1160 krate.update(&self.db_con).await?;
1161 Ok(())
1162 }
1163
1164 async fn is_crate_user(&self, crate_name: &NormalizedName, user: &str) -> DbResult<bool> {
1165 Ok(self
1166 .get_crate_user_by_crate_and_user(crate_name, user)
1167 .await?
1168 .is_some())
1169 }
1170
1171 async fn is_crate_group(&self, crate_name: &NormalizedName, group: &str) -> DbResult<bool> {
1172 Ok(self
1173 .get_crate_group_by_name_and_group(crate_name, group)
1174 .await?
1175 .is_some())
1176 }
1177
1178 async fn is_crate_group_user(&self, crate_name: &NormalizedName, user: &str) -> DbResult<bool> {
1179 let user = user::Entity::find()
1180 .join(JoinType::InnerJoin, user::Relation::GroupUser.def())
1181 .join(JoinType::InnerJoin, group_user::Relation::Group.def())
1182 .join(JoinType::InnerJoin, group::Relation::CrateGroup.def())
1183 .join(JoinType::InnerJoin, crate_group::Relation::Krate.def())
1184 .filter(
1185 Cond::all()
1186 .add(krate::Column::Name.eq(crate_name))
1187 .add(user::Column::Name.eq(user)),
1188 )
1189 .one(&self.db_con)
1190 .await?;
1191 Ok(user.is_some())
1192 }
1193
1194 async fn is_group_user(&self, group_name: &str, user: &str) -> DbResult<bool> {
1195 Ok(self
1196 .get_group_user_by_group_and_user(group_name, user)
1197 .await?
1198 .is_some())
1199 }
1200
1201 async fn is_owner(&self, crate_name: &NormalizedName, user: &str) -> DbResult<bool> {
1202 let owner = owner::Entity::find()
1203 .join(JoinType::InnerJoin, owner::Relation::Krate.def())
1204 .join(JoinType::InnerJoin, owner::Relation::User.def())
1205 .filter(
1206 Cond::all()
1207 .add(krate::Column::Name.eq(crate_name))
1208 .add(user::Column::Name.eq(user)),
1209 )
1210 .one(&self.db_con)
1211 .await?;
1212
1213 Ok(owner.is_some())
1214 }
1215
1216 async fn get_crate_id(&self, crate_name: &NormalizedName) -> DbResult<Option<i64>> {
1217 let id = krate::Entity::find()
1218 .filter(krate::Column::Name.eq(crate_name))
1219 .one(&self.db_con)
1220 .await?
1221 .map(|model| model.id);
1222
1223 Ok(id)
1224 }
1225
1226 async fn get_crate_owners(&self, crate_name: &NormalizedName) -> DbResult<Vec<User>> {
1227 let u = user::Entity::find()
1228 .join(JoinType::InnerJoin, user::Relation::Owner.def())
1229 .join(JoinType::InnerJoin, owner::Relation::Krate.def())
1230 .filter(Expr::col((CrateIden::Table, krate::Column::Name)).eq(crate_name))
1231 .all(&self.db_con)
1232 .await?;
1233
1234 Ok(u.into_iter().map(User::from).collect())
1235 }
1236
1237 async fn get_crate_users(&self, crate_name: &NormalizedName) -> DbResult<Vec<User>> {
1238 let u = user::Entity::find()
1239 .join(JoinType::InnerJoin, user::Relation::CrateUser.def())
1240 .join(JoinType::InnerJoin, crate_user::Relation::Krate.def())
1241 .filter(Expr::col((CrateIden::Table, krate::Column::Name)).eq(crate_name))
1242 .all(&self.db_con)
1243 .await?;
1244
1245 Ok(u.into_iter().map(User::from).collect())
1246 }
1247
1248 async fn get_crate_groups(&self, crate_name: &NormalizedName) -> DbResult<Vec<Group>> {
1249 let u = group::Entity::find()
1250 .join(JoinType::InnerJoin, group::Relation::CrateGroup.def())
1251 .join(JoinType::InnerJoin, crate_group::Relation::Krate.def())
1252 .filter(Expr::col((CrateIden::Table, krate::Column::Name)).eq(crate_name))
1253 .all(&self.db_con)
1254 .await?;
1255
1256 Ok(u.into_iter().map(Group::from).collect())
1257 }
1258
1259 async fn get_group_users(&self, group_name: &str) -> DbResult<Vec<User>> {
1260 let u = user::Entity::find()
1261 .join(JoinType::InnerJoin, user::Relation::GroupUser.def())
1262 .join(JoinType::InnerJoin, group_user::Relation::Group.def())
1263 .filter(Expr::col((GroupIden::Table, group::Column::Name)).eq(group_name))
1264 .all(&self.db_con)
1265 .await?;
1266
1267 Ok(u.into_iter().map(User::from).collect())
1268 }
1269
1270 async fn get_crate_versions(&self, crate_name: &NormalizedName) -> DbResult<Vec<Version>> {
1271 let u = crate_meta::Entity::find()
1272 .join(JoinType::InnerJoin, crate_meta::Relation::Krate.def())
1273 .filter(Expr::col((CrateIden::Table, krate::Column::Name)).eq(crate_name))
1274 .all(&self.db_con)
1275 .await?;
1276
1277 Ok(u.into_iter()
1278 .map(|meta| Version::from_unchecked_str(&meta.version))
1279 .collect())
1280 }
1281
1282 async fn delete_session_token(&self, session_token: &str) -> DbResult<()> {
1283 if let Some(s) = session::Entity::find()
1284 .filter(session::Column::Token.eq(hash_token(session_token)))
1285 .one(&self.db_con)
1286 .await?
1287 {
1288 s.delete(&self.db_con).await?;
1289 }
1290
1291 Ok(())
1292 }
1293
1294 async fn delete_user(&self, user_name: &str) -> DbResult<()> {
1295 self.get_user_model(user_name)
1296 .await?
1297 .delete(&self.db_con)
1298 .await?;
1299 Ok(())
1300 }
1301
1302 async fn delete_group(&self, group_name: &str) -> DbResult<()> {
1303 self.get_group_model(group_name)
1304 .await?
1305 .delete(&self.db_con)
1306 .await?;
1307 Ok(())
1308 }
1309
1310 async fn change_pwd(&self, user_name: &str, new_pwd: &str) -> DbResult<()> {
1311 let hashed = hash_pwd(new_pwd).await;
1312
1313 let mut u: user::ActiveModel = self.get_user_model(user_name).await?.into();
1314 u.pwd = Set(hashed);
1315 u.salt = Set(String::new());
1317
1318 u.update(&self.db_con).await?;
1319 Ok(())
1320 }
1321
1322 async fn change_read_only_state(&self, user_name: &str, state: bool) -> DbResult<()> {
1323 let mut u: user::ActiveModel = self.get_user_model(user_name).await?.into();
1324 u.is_read_only = Set(state);
1325
1326 u.update(&self.db_con).await?;
1327 Ok(())
1328 }
1329
1330 async fn change_admin_state(&self, user_name: &str, state: bool) -> DbResult<()> {
1331 let mut u: user::ActiveModel = self.get_user_model(user_name).await?.into();
1332 u.is_admin = Set(state);
1333
1334 u.update(&self.db_con).await?;
1335 Ok(())
1336 }
1337
1338 async fn crate_version_exists(&self, crate_id: i64, version: &str) -> DbResult<bool> {
1339 let cm = crate_meta::Entity::find()
1340 .filter(
1341 Cond::all()
1342 .add(crate_meta::Column::CrateFk.eq(crate_id))
1343 .add(crate_meta::Column::Version.eq(version)),
1344 )
1345 .one(&self.db_con)
1346 .await?;
1347
1348 Ok(cm.is_some())
1349 }
1350
1351 async fn get_max_version_from_id(&self, crate_id: i64) -> DbResult<Version> {
1352 operations::get_max_version_from_id(&self.db_con, crate_id).await
1353 }
1354
1355 async fn get_max_version_from_name(&self, crate_name: &NormalizedName) -> DbResult<Version> {
1356 let k = self.get_krate_model(crate_name).await?;
1357 let v = Version::try_from(&k.max_version)
1358 .map_err(|_| DbError::FailedToGetMaxVersionByName(crate_name.to_string()))?;
1359 Ok(v)
1360 }
1361
1362 async fn update_max_version(&self, crate_id: i64, version: &Version) -> DbResult<()> {
1363 let krate = krate::Entity::find_by_id(crate_id)
1364 .one(&self.db_con)
1365 .await?
1366 .ok_or(DbError::CrateNotFoundWithId(crate_id))?;
1367
1368 let mut k: krate::ActiveModel = krate.into();
1369 k.max_version = Set(version.to_string());
1370 k.update(&self.db_con).await?;
1371
1372 Ok(())
1373 }
1374
1375 async fn add_auth_token(&self, name: &str, token: &str, user: &str) -> DbResult<()> {
1376 let hashed_token = hash_token(token);
1377 let user = self.get_user_model(user).await?;
1378
1379 let at = auth_token::ActiveModel {
1380 name: Set(name.to_owned()),
1381 token: Set(hashed_token),
1382 user_fk: Set(user.id),
1383 created: Set(Some(Utc::now().format(DB_DATE_FORMAT).to_string())),
1384 ..Default::default()
1385 };
1386
1387 at.insert(&self.db_con).await?;
1388
1389 Ok(())
1390 }
1391
1392 async fn get_user_from_token(&self, token: &str) -> DbResult<User> {
1393 let token = hash_token(token);
1394
1395 let (at, u) = auth_token::Entity::find()
1396 .filter(Expr::col((AuthTokenIden::Table, AuthTokenIden::Token)).eq(token))
1397 .find_also_related(user::Entity)
1398 .one(&self.db_con)
1399 .await?
1400 .ok_or(DbError::TokenNotFound)?;
1401 let u = u.ok_or(DbError::TokenNotFound)?;
1402
1403 let now = Utc::now();
1407 let stale_before = (now - TOKEN_LAST_USED_INTERVAL)
1408 .format(DB_DATE_FORMAT)
1409 .to_string();
1410 if at
1411 .last_used
1412 .as_ref()
1413 .is_none_or(|used| *used < stale_before)
1414 {
1415 let mut at: auth_token::ActiveModel = at.into();
1416 at.last_used = Set(Some(now.format(DB_DATE_FORMAT).to_string()));
1417 if let Err(e) = at.update(&self.db_con).await {
1418 warn!("Failed to update the last use of an auth token: {e}");
1419 }
1420 }
1421
1422 Ok(User::from(u))
1423 }
1424
1425 async fn get_user(&self, name: &str) -> DbResult<User> {
1426 Ok(User::from(self.get_user_model(name).await?))
1427 }
1428
1429 async fn get_group(&self, name: &str) -> DbResult<Group> {
1430 Ok(Group::from(self.get_group_model(name).await?))
1431 }
1432
1433 async fn get_auth_token_name(&self, token: &str) -> DbResult<Option<String>> {
1434 Ok(auth_token::Entity::find()
1435 .filter(auth_token::Column::Token.eq(hash_token(token)))
1436 .one(&self.db_con)
1437 .await?
1438 .map(|at| at.name))
1439 }
1440
1441 async fn get_auth_tokens(&self, user_name: &str) -> DbResult<Vec<AuthToken>> {
1442 let at: Vec<auth_token::Model> = auth_token::Entity::find()
1443 .join(JoinType::InnerJoin, auth_token::Relation::User.def())
1444 .filter(user::Column::Name.eq(user_name))
1445 .all(&self.db_con)
1446 .await?;
1447
1448 Ok(at.into_iter().map(AuthToken::from).collect())
1449 }
1450
1451 async fn delete_auth_token(&self, id: i32) -> DbResult<()> {
1452 auth_token::Entity::delete_by_id(id as i64)
1453 .exec(&self.db_con)
1454 .await?;
1455 Ok(())
1456 }
1457
1458 async fn delete_owner(&self, crate_name: &str, owner: &str) -> DbResult<()> {
1459 let model = self.get_owner_by_crate_and_user(crate_name, owner).await?;
1460 self.delete_or_not_found(model, DbError::OwnerNotFound(owner.to_string()))
1461 .await
1462 }
1463
1464 async fn delete_crate_user(&self, crate_name: &NormalizedName, user: &str) -> DbResult<()> {
1465 let model = self
1466 .get_crate_user_by_crate_and_user(crate_name, user)
1467 .await?;
1468 self.delete_or_not_found(model, DbError::UserNotFound(user.to_string()))
1469 .await
1470 }
1471
1472 async fn delete_crate_group(&self, crate_name: &NormalizedName, group: &str) -> DbResult<()> {
1473 let model = self
1474 .get_crate_group_by_name_and_group(crate_name, group)
1475 .await?;
1476 self.delete_or_not_found(model, DbError::GroupNotFound(group.to_string()))
1477 .await
1478 }
1479
1480 async fn delete_group_user(&self, group_name: &str, user: &str) -> DbResult<()> {
1481 let model = self
1482 .get_group_user_by_group_and_user(group_name, user)
1483 .await?;
1484 self.delete_or_not_found(model, DbError::UserNotFound(user.to_string()))
1485 .await
1486 }
1487
1488 async fn add_user(
1489 &self,
1490 name: &str,
1491 pwd: &str,
1492 is_admin: bool,
1493 is_read_only: bool,
1494 ) -> DbResult<()> {
1495 let hashed_pwd = hash_pwd(pwd).await;
1496 let created = Utc::now().format(DB_DATE_FORMAT).to_string();
1497
1498 let u = user::ActiveModel {
1499 name: Set(name.to_owned()),
1500 pwd: Set(hashed_pwd),
1501 salt: Set(String::new()),
1502 is_admin: Set(is_admin),
1503 is_read_only: Set(is_read_only),
1504 created: Set(created),
1505 ..Default::default()
1506 };
1507
1508 u.insert(&self.db_con).await?;
1509 Ok(())
1510 }
1511
1512 async fn add_group(&self, name: &str) -> DbResult<()> {
1513 let g = group::ActiveModel {
1514 name: Set(name.to_owned()),
1515 ..Default::default()
1516 };
1517
1518 g.insert(&self.db_con).await?;
1519 Ok(())
1520 }
1521
1522 async fn get_users(&self) -> DbResult<Vec<User>> {
1523 let users = user::Entity::find()
1524 .order_by_asc(user::Column::Name)
1525 .all(&self.db_con)
1526 .await?;
1527
1528 Ok(users.into_iter().map(User::from).collect())
1529 }
1530
1531 async fn get_groups(&self) -> DbResult<Vec<Group>> {
1532 let groups = group::Entity::find()
1533 .order_by_asc(group::Column::Name)
1534 .all(&self.db_con)
1535 .await?;
1536
1537 Ok(groups.into_iter().map(Group::from).collect())
1538 }
1539
1540 async fn get_total_unique_crates(&self) -> DbResult<u64> {
1541 self.count(
1542 CrateIden::Table,
1543 CrateIden::Id,
1544 DbError::FailedToCountCrates,
1545 )
1546 .await
1547 }
1548
1549 async fn get_total_crate_versions(&self) -> DbResult<u64> {
1550 self.count(
1551 CrateMetaIden::Table,
1552 CrateMetaIden::Id,
1553 DbError::FailedToCountCrateVersions,
1554 )
1555 .await
1556 }
1557
1558 async fn get_total_downloads(&self) -> DbResult<u64> {
1559 #[derive(FromQueryResult)]
1560 struct Model {
1561 total_downloads: i64,
1562 }
1563
1564 Ok(krate::Entity::find()
1565 .select_only()
1566 .column(krate::Column::TotalDownloads)
1567 .into_model::<Model>()
1568 .all(&self.db_con)
1569 .await?
1570 .into_iter()
1571 .map(|m| m.total_downloads as u64)
1572 .sum())
1573 }
1574
1575 async fn get_top_crates_downloads(
1576 &self,
1577 top: u64,
1578 visibility: &CrateVisibility,
1579 ) -> DbResult<Vec<(String, u64)>> {
1580 #[derive(Debug, PartialEq, FromQueryResult)]
1581 struct SelectResult {
1582 original_name: String,
1583 total_downloads: i64,
1584 }
1585
1586 let mut query = Query::select()
1587 .columns(vec![CrateIden::OriginalName, CrateIden::TotalDownloads])
1588 .from(CrateIden::Table)
1589 .order_by(CrateIden::TotalDownloads, Order::Desc)
1590 .limit(top)
1591 .to_owned();
1592 if let Some(condition) = Self::crate_visibility_condition(visibility) {
1593 query.cond_where(condition);
1594 }
1595 let stmt = self.db_con.get_database_backend().build(&query);
1596
1597 Ok(SelectResult::find_by_statement(stmt)
1598 .all(&self.db_con)
1599 .await?
1600 .into_iter()
1601 .map(|x| (x.original_name, x.total_downloads as u64))
1602 .collect())
1603 }
1604
1605 async fn get_crate_summaries(&self) -> DbResult<Vec<CrateSummary>> {
1606 let krates = krate::Entity::find()
1607 .order_by(krate::Column::Name, Order::Asc)
1608 .all(&self.db_con)
1609 .await?;
1610
1611 Ok(krates.into_iter().map(CrateSummary::from).collect())
1612 }
1613
1614 async fn add_doc_queue(
1615 &self,
1616 krate: &NormalizedName,
1617 version: &Version,
1618 path: &Path,
1619 ) -> DbResult<()> {
1620 let s = doc_queue::ActiveModel {
1621 krate: Set(krate.to_string()),
1622 version: Set(version.to_string()),
1623 path: Set(path.to_string_lossy().to_string()),
1625 ..Default::default()
1626 };
1627
1628 s.insert(&self.db_con).await?;
1629 Ok(())
1630 }
1631
1632 async fn delete_doc_queue(&self, id: i64) -> DbResult<()> {
1633 DocQueue::delete_by_id(id).exec(&self.db_con).await?;
1634 Ok(())
1635 }
1636
1637 async fn get_doc_queue(&self) -> DbResult<Vec<DocQueueEntry>> {
1638 let entities = DocQueue::find().all(&self.db_con).await?;
1639
1640 Ok(entities.into_iter().map(DocQueueEntry::from).collect())
1641 }
1642
1643 async fn delete_crate(&self, krate: &NormalizedName, version: &Version) -> DbResult<()> {
1644 let txn = self.db_con.begin().await?;
1645
1646 let crate_meta_version = crate_meta::Entity::find()
1648 .join(JoinType::InnerJoin, crate_meta::Relation::Krate.def())
1649 .filter(krate::Column::Name.eq(krate))
1650 .filter(crate_meta::Column::Version.eq(version))
1651 .one(&txn)
1652 .await?
1653 .ok_or_else(|| DbError::CrateMetaNotFound(krate.to_string(), version.to_string()))?;
1654 let crate_id = crate_meta_version.crate_fk;
1655 let current_max_version = operations::get_max_version_from_id(&txn, crate_id).await?;
1656 crate_meta_version.delete(&txn).await?;
1657
1658 let crate_index_version = crate_index::Entity::find()
1660 .join(JoinType::InnerJoin, crate_index::Relation::Krate.def())
1661 .filter(krate::Column::Name.eq(krate))
1662 .filter(crate_index::Column::Vers.eq(version))
1663 .one(&txn)
1664 .await?
1665 .ok_or_else(|| DbError::CrateIndexNotFound(krate.to_string(), version.to_string()))?;
1666 crate_index_version.delete(&txn).await?;
1667
1668 let crate_meta_rows = crate_meta::Entity::find()
1671 .join(JoinType::InnerJoin, crate_meta::Relation::Krate.def())
1672 .filter(krate::Column::Name.eq(krate))
1673 .all(&txn)
1674 .await?;
1675
1676 if crate_meta_rows.is_empty() {
1677 krate::Entity::delete_many()
1678 .filter(krate::Column::Name.eq(krate))
1679 .exec(&txn)
1680 .await?;
1681 } else {
1682 let c = krate::Entity::find_by_id(crate_id)
1683 .one(&txn)
1684 .await?
1685 .ok_or(DbError::CrateNotFoundWithId(crate_id))?;
1686 let mut c: krate::ActiveModel = c.into();
1687
1688 if version == ¤t_max_version {
1690 let new_max_version = crate_meta_rows
1691 .into_iter()
1692 .map(|cm| Version::from_unchecked_str(&cm.version))
1693 .max()
1694 .unwrap(); c.max_version = Set(new_max_version.into_inner());
1696 }
1697 let etag = operations::compute_etag(&txn, krate, crate_id).await?;
1699 c.e_tag = Set(etag);
1700 c.update(&txn).await?;
1701 }
1702
1703 txn.commit().await?;
1704
1705 Ok(())
1706 }
1707
1708 async fn get_crate_meta_list(&self, crate_name: &NormalizedName) -> DbResult<Vec<CrateMeta>> {
1709 let crate_meta = crate_meta::Entity::find()
1710 .join(JoinType::InnerJoin, crate_meta::Relation::Krate.def())
1711 .filter(krate::Column::Name.eq(crate_name))
1712 .all(&self.db_con)
1713 .await?;
1714
1715 let crate_meta = crate_meta
1716 .into_iter()
1717 .map(|cm| CrateMeta {
1718 name: crate_name.to_string(),
1719 id: cm.id,
1720 version: cm.version,
1721 created: cm.created,
1722 downloads: cm.downloads,
1723 crate_fk: cm.crate_fk,
1724 })
1725 .collect();
1726
1727 Ok(crate_meta)
1728 }
1729
1730 async fn update_last_updated(&self, id: i64, last_updated: &DateTime<Utc>) -> DbResult<()> {
1731 let krate = krate::Entity::find_by_id(id)
1732 .one(&self.db_con)
1733 .await?
1734 .ok_or(DbError::CrateNotFoundWithId(id))?;
1735
1736 let date = last_updated.format(DB_DATE_FORMAT).to_string();
1737
1738 let mut krate: krate::ActiveModel = krate.into();
1739 krate.last_updated = Set(date);
1740 krate.update(&self.db_con).await?;
1741
1742 Ok(())
1743 }
1744
1745 async fn search_in_crate_name_and_description(
1746 &self,
1747 contains: &str,
1748 limit: u64,
1749 offset: u64,
1750 cache: bool,
1751 visibility: &CrateVisibility,
1752 ) -> DbResult<Vec<CrateOverview>> {
1753 self.query_crates(
1754 Some(contains),
1755 Some((limit, offset)),
1756 cache,
1757 visibility,
1758 CrateSort::Name,
1759 &CrateTagFilter::default(),
1760 )
1761 .await
1762 }
1763
1764 async fn count_by_crate_name_and_description(
1765 &self,
1766 contains: &str,
1767 cache: bool,
1768 visibility: &CrateVisibility,
1769 ) -> DbResult<u64> {
1770 self.count_crates(
1771 Some(contains),
1772 cache,
1773 visibility,
1774 &CrateTagFilter::default(),
1775 )
1776 .await
1777 }
1778
1779 async fn get_crate_overview_list(
1780 &self,
1781 limit: u64,
1782 offset: u64,
1783 cache: bool,
1784 visibility: &CrateVisibility,
1785 sort: CrateSort,
1786 tags: &CrateTagFilter,
1787 ) -> DbResult<Vec<CrateOverview>> {
1788 self.query_crates(None, Some((limit, offset)), cache, visibility, sort, tags)
1789 .await
1790 }
1791
1792 async fn count_crate_overview_list(
1793 &self,
1794 cache: bool,
1795 visibility: &CrateVisibility,
1796 tags: &CrateTagFilter,
1797 ) -> DbResult<u64> {
1798 self.count_crates(None, cache, visibility, tags).await
1799 }
1800
1801 async fn get_crate_data(&self, crate_name: &NormalizedName) -> DbResult<CrateData> {
1802 let krate = self.get_krate_model(crate_name).await?;
1803
1804 let owners: Vec<String> = krate
1805 .find_related(owner::Entity)
1806 .find_also_related(user::Entity)
1807 .all(&self.db_con)
1808 .await?
1809 .into_iter()
1810 .filter_map(|(_, v)| v.map(|v| v.name))
1811 .collect();
1812 let categories: Vec<String> = krate
1813 .find_related(crate_category_to_crate::Entity)
1814 .find_also_related(crate_category::Entity)
1815 .all(&self.db_con)
1816 .await?
1817 .into_iter()
1818 .filter_map(|(_, v)| v.map(|v| v.category))
1819 .collect();
1820 let keywords: Vec<String> = krate
1821 .find_related(crate_keyword_to_crate::Entity)
1822 .find_also_related(crate_keyword::Entity)
1823 .all(&self.db_con)
1824 .await?
1825 .into_iter()
1826 .filter_map(|(_, v)| v.map(|v| v.keyword))
1827 .collect();
1828 let authors: Vec<String> = krate
1829 .find_related(crate_author_to_crate::Entity)
1830 .find_also_related(crate_author::Entity)
1831 .all(&self.db_con)
1832 .await?
1833 .into_iter()
1834 .filter_map(|(_, v)| v.map(|v| v.author))
1835 .collect();
1836 let crate_metas = krate
1837 .find_related(crate_meta::Entity)
1838 .all(&self.db_con)
1839 .await?;
1840 let crate_indices = krate
1841 .find_related(crate_index::Entity)
1842 .all(&self.db_con)
1843 .await?;
1844
1845 let mut index_deps: HashMap<&str, Vec<IndexDep>> = HashMap::new();
1847 for ci in &crate_indices {
1848 if let Some(deps) = ci.deps.clone() {
1849 let deps = serde_json::from_value::<Vec<IndexDep>>(deps)
1850 .map_err(|e| DbError::FailedToConvertToJson(e.to_string()))?;
1851 index_deps.insert(ci.vers.as_str(), deps);
1852 }
1853 }
1854 let dep_info =
1855 operations::get_dep_info(&self.db_con, index_deps.values().flatten()).await?;
1856
1857 let mut versions = Vec::new();
1858 for cm in crate_metas {
1859 let ci = crate_indices
1860 .iter()
1861 .find(|ci| ci.vers == cm.version)
1862 .ok_or_else(|| {
1863 DbError::CrateIndexNotFound(krate.name.clone(), cm.version.clone())
1864 })?;
1865 let dependencies: Vec<CrateRegistryDep> = index_deps
1866 .get(ci.vers.as_str())
1867 .into_iter()
1868 .flatten()
1869 .map(|dep| {
1870 let info = dep_info
1871 .get(&operations::dep_lookup_name(dep))
1872 .cloned()
1873 .unwrap_or_default();
1874 CrateRegistryDep::from_index(info.description, info.latest_version, dep.clone())
1875 })
1876 .collect();
1877 let features: BTreeMap<String, Vec<String>> = match ci.features.clone() {
1878 Some(features) => serde_json::from_value::<BTreeMap<String, Vec<String>>>(features)
1879 .map_err(|e| DbError::FailedToConvertToJson(e.to_string()))?,
1880 None => BTreeMap::default(),
1881 };
1882
1883 versions.push(CrateVersionData {
1884 version: cm.version,
1885 created: cm.created,
1886 downloads: cm.downloads,
1887 readme: cm.readme,
1888 license: cm.license,
1889 license_file: cm.license_file,
1890 documentation: cm.documentation,
1891 dependencies,
1892 checksum: ci.cksum.clone(),
1893 features,
1894 yanked: ci.yanked,
1895 links: ci.links.clone(),
1896 v: ci.v,
1897 size: cm.size,
1898 rust_version: ci.rust_version.clone(),
1899 publisher: cm.publisher.clone(),
1900 publisher_token: cm.publisher_token.clone(),
1901 });
1902 }
1903 versions.sort_by(|a, b| {
1904 Version::from_unchecked_str(&b.version).cmp(&Version::from_unchecked_str(&a.version))
1905 });
1906
1907 let crate_data = CrateData {
1908 name: krate.original_name,
1909 owners,
1910 max_version: krate.max_version,
1911 total_downloads: krate.total_downloads,
1912 last_updated: krate.last_updated,
1913 homepage: krate.homepage,
1914 description: krate.description,
1915 repository: krate.repository,
1916 categories,
1917 keywords,
1918 authors,
1919 versions,
1920 };
1921
1922 Ok(crate_data)
1923 }
1924
1925 async fn get_crate_dependents(&self, crate_name: &NormalizedName) -> DbResult<CrateDependents> {
1926 let krate = self.get_krate_model(crate_name).await?;
1927
1928 let requirements: Vec<(i64, String, String, String)> = crate_dependency::Entity::find()
1930 .select_only()
1931 .column(crate_index::Column::CrateFk)
1932 .column(crate_index::Column::Vers)
1933 .column(crate_dependency::Column::VersionReq)
1934 .column(crate_dependency::Column::Kind)
1935 .inner_join(crate_index::Entity)
1936 .filter(crate_dependency::Column::Name.eq(crate_name))
1937 .filter(crate_index::Column::Yanked.eq(false))
1938 .filter(crate_index::Column::CrateFk.ne(krate.id))
1939 .into_tuple()
1940 .all(&self.db_con)
1941 .await?;
1942 let mut requirements_by_version: HashMap<(i64, String), Vec<(String, String)>> =
1943 HashMap::new();
1944 for (crate_fk, vers, version_req, kind) in requirements {
1945 requirements_by_version
1946 .entry((crate_fk, vers))
1947 .or_default()
1948 .push((version_req, kind));
1949 }
1950
1951 let mut crate_ids: Vec<i64> = requirements_by_version
1954 .keys()
1955 .map(|(crate_fk, _)| *crate_fk)
1956 .collect();
1957 crate_ids.push(krate.id);
1958 crate_ids.sort_unstable();
1959 crate_ids.dedup();
1960 let mut versions_by_crate: HashMap<i64, (String, Vec<String>)> = HashMap::new();
1961 for ids in crate_ids.chunks(1000) {
1963 let versions: Vec<(i64, String, String)> = crate_index::Entity::find()
1964 .select_only()
1965 .column(crate_index::Column::CrateFk)
1966 .column(krate::Column::OriginalName)
1967 .column(crate_index::Column::Vers)
1968 .inner_join(krate::Entity)
1969 .filter(crate_index::Column::CrateFk.is_in(ids.iter().copied()))
1970 .filter(crate_index::Column::Yanked.eq(false))
1971 .into_tuple()
1972 .all(&self.db_con)
1973 .await?;
1974 for (crate_fk, name, vers) in versions {
1975 versions_by_crate
1976 .entry(crate_fk)
1977 .or_insert_with(|| (name, Vec::new()))
1978 .1
1979 .push(vers);
1980 }
1981 }
1982
1983 let latest = versions_by_crate
1984 .remove(&krate.id)
1985 .and_then(|(_, versions)| latest_version(versions.iter().map(String::as_str)));
1986 let mut dependents: Vec<CrateDependent> = versions_by_crate
1987 .into_iter()
1988 .filter_map(|(crate_fk, (name, versions))| {
1989 let version = latest_version(versions.iter().map(String::as_str))?;
1990 let requirements = requirements_by_version.remove(&(crate_fk, version.clone()))?;
1991 Some(CrateDependent::new(
1992 name,
1993 version,
1994 requirements,
1995 latest.as_deref(),
1996 ))
1997 })
1998 .collect();
1999 dependents.sort_by(|a, b| a.name.cmp(&b.name));
2000
2001 Ok(CrateDependents {
2002 latest_version: latest,
2003 dependents,
2004 restricted: 0,
2005 })
2006 }
2007
2008 async fn add_empty_crate(&self, name: &str, created: &DateTime<Utc>) -> DbResult<i64> {
2009 let created = created.format(DB_DATE_FORMAT).to_string();
2010 let normalized_name = NormalizedName::from(
2011 OriginalName::try_from(name)
2012 .map_err(|_| DbError::InvalidCrateName(name.to_string()))?,
2013 );
2014 operations::ensure_no_similar_crate(&self.db_con, &normalized_name).await?;
2015 let krate = krate::ActiveModel {
2016 id: ActiveValue::default(),
2017 name: Set(normalized_name.to_string()),
2018 original_name: Set(name.to_string()),
2019 max_version: Set("0.0.0".to_string()),
2020 last_updated: Set(created),
2021 total_downloads: Set(0),
2022 homepage: Set(None),
2023 description: Set(None),
2024 repository: Set(None),
2025 e_tag: Set(String::new()), restricted_download: Set(false),
2027 };
2028 Ok(krate.insert(&self.db_con).await?.id)
2029 }
2030
2031 async fn add_crate(
2032 &self,
2033 pub_metadata: &PublishMetadata,
2034 cksum: &str,
2035 size: i64,
2036 created: &DateTime<Utc>,
2037 owner: &str,
2038 token_name: Option<String>,
2039 ) -> DbResult<i64> {
2040 let created = created.format(DB_DATE_FORMAT).to_string();
2041 let normalized_name = NormalizedName::from(
2042 OriginalName::try_from(&pub_metadata.name)
2043 .map_err(|_| DbError::InvalidCrateName(pub_metadata.name.clone()))?,
2044 );
2045
2046 let txn = self.db_con.begin().await?;
2049 let existing = krate::Entity::find()
2050 .filter(krate::Column::Name.eq(&normalized_name))
2051 .one(&txn)
2052 .await?;
2053
2054 let crate_id = if let Some(krate) = existing {
2055 let krate_id = krate.id;
2056 operations::ensure_owner_or_admin(&txn, owner, &normalized_name, krate_id).await?;
2057 let max_version = max(
2058 parse_db_version(&krate.max_version)?,
2059 parse_db_version(&pub_metadata.vers)?,
2060 );
2061
2062 let mut krate: krate::ActiveModel = krate.into();
2063 krate.last_updated = Set(created.clone());
2064 krate.max_version = Set(max_version.into_inner());
2065 krate.homepage = Set(pub_metadata.homepage.clone());
2066 krate.description = Set(pub_metadata.description.clone());
2067 krate.repository = Set(pub_metadata.repository.clone());
2068 krate.e_tag = Set(String::new()); krate.update(&txn).await?;
2070 krate_id
2071 } else {
2072 operations::ensure_no_similar_crate(&txn, &normalized_name).await?;
2073 let krate = krate::ActiveModel {
2074 id: ActiveValue::default(),
2075 name: Set(normalized_name.to_string()),
2076 original_name: Set(pub_metadata.name.clone()),
2077 max_version: Set(pub_metadata.vers.clone()),
2078 last_updated: Set(created.clone()),
2079 total_downloads: Set(0),
2080 homepage: Set(pub_metadata.homepage.clone()),
2081 description: Set(pub_metadata.description.clone()),
2082 repository: Set(pub_metadata.repository.clone()),
2083 e_tag: Set(String::new()), restricted_download: Set(false),
2085 };
2086 let krate = krate.insert(&txn).await?;
2087 krate.id
2088 };
2089
2090 operations::add_owner_if_not_exists(&txn, owner, crate_id).await?;
2091 operations::add_crate_metadata(
2092 &txn,
2093 pub_metadata,
2094 size,
2095 &created,
2096 crate_id,
2097 Some(owner),
2098 token_name.as_deref(),
2099 )
2100 .await?;
2101 operations::add_crate_index(&txn, pub_metadata, cksum, crate_id).await?;
2102 operations::update_etag(&txn, &pub_metadata.name, crate_id).await?;
2103 operations::update_crate_categories(&txn, pub_metadata, crate_id).await?;
2104 operations::update_crate_keywords(&txn, pub_metadata, crate_id).await?;
2105 operations::update_crate_authors(&txn, pub_metadata, crate_id).await?;
2106
2107 txn.commit().await?;
2108 Ok(crate_id)
2109 }
2110
2111 async fn update_docs_link(
2112 &self,
2113 crate_name: &NormalizedName,
2114 version: &Version,
2115 docs_link: &str,
2116 ) -> DbResult<()> {
2117 let (cm, _c) = crate_meta::Entity::find()
2118 .find_also_related(krate::Entity)
2119 .filter(
2120 Cond::all()
2121 .add(krate::Column::Name.eq(crate_name))
2122 .add(crate_meta::Column::Version.eq(version)),
2123 )
2124 .one(&self.db_con)
2125 .await?
2126 .ok_or_else(|| DbError::CrateNotFound(crate_name.to_string()))?;
2127
2128 let mut cm: crate_meta::ActiveModel = cm.into();
2129 cm.documentation = Set(Some(docs_link.to_string()));
2130 cm.update(&self.db_con).await?;
2131 Ok(())
2132 }
2133
2134 async fn add_crate_metadata(
2135 &self,
2136 pub_metadata: &PublishMetadata,
2137 size: i64,
2138 created: &str,
2139 crate_id: i64,
2140 ) -> DbResult<()> {
2141 operations::add_crate_metadata(
2142 &self.db_con,
2143 pub_metadata,
2144 size,
2145 created,
2146 crate_id,
2147 None,
2148 None,
2149 )
2150 .await
2151 }
2152
2153 async fn get_prefetch_data(&self, crate_name: &str) -> DbResult<Prefetch> {
2154 let mut krate = krate::Entity::find()
2155 .filter(krate::Column::Name.eq(crate_name))
2156 .find_with_related(crate_index::Entity)
2157 .all(&self.db_con)
2158 .await?
2159 .into_iter();
2160
2161 let (Some((krate, crate_indices)), None) = (krate.next(), krate.next()) else {
2163 return Err(DbError::CrateNotFound(crate_name.to_string()));
2164 };
2165
2166 let index_metadata =
2167 operations::crate_index_model_to_index_metadata(crate_name, crate_indices)?;
2168 let data = operations::index_metadata_to_bytes(&index_metadata)?;
2169
2170 Ok(Prefetch {
2171 data,
2172 etag: krate.e_tag,
2173 last_modified: krate.last_updated,
2174 })
2175 }
2176
2177 async fn is_cratesio_cache_up_to_date(
2178 &self,
2179 crate_name: &NormalizedName,
2180 etag: Option<String>,
2181 last_modified: Option<String>,
2182 ) -> DbResult<PrefetchState> {
2183 let Some(krate) = cratesio_crate::Entity::find()
2184 .filter(cratesio_crate::Column::Name.eq(crate_name))
2185 .one(&self.db_con)
2186 .await?
2187 else {
2188 return Ok(PrefetchState::NotFound);
2189 };
2190
2191 let needs_update = match (etag, last_modified) {
2192 (Some(etag), Some(last_modified)) => {
2193 krate.e_tag != etag || krate.last_modified != last_modified
2194 }
2195 (Some(etag), None) => krate.e_tag != etag,
2196 (None, Some(last_modified)) => krate.last_modified != last_modified,
2197 (None, None) => true,
2198 };
2199
2200 if !needs_update {
2201 Ok(PrefetchState::UpToDate {
2202 etag: krate.e_tag,
2203 last_modified: krate.last_modified,
2204 })
2205 } else {
2206 let crate_indices = krate
2207 .find_related(cratesio_index::Entity)
2208 .all(&self.db_con)
2209 .await?;
2210 let index_metadata =
2211 operations::cratesio_index_model_to_index_metadata(crate_name, crate_indices)?;
2212 let data = operations::index_metadata_to_bytes(&index_metadata)?;
2213
2214 Ok(PrefetchState::NeedsUpdate(Prefetch {
2215 data,
2216 etag: krate.e_tag.clone(),
2217 last_modified: krate.last_modified,
2218 }))
2219 }
2220 }
2221
2222 async fn add_cratesio_prefetch_data(
2223 &self,
2224 crate_name: &OriginalName,
2225 etag: &str,
2226 last_modified: &str,
2227 description: Option<String>,
2228 indices: &[IndexMetadata],
2229 licenses: &HashMap<String, String>,
2230 ) -> DbResult<Prefetch> {
2231 let normalized_name = crate_name.to_normalized();
2232
2233 let max_version = indices
2234 .iter()
2235 .map(|i| Version::from_unchecked_str(&i.vers))
2236 .max()
2237 .ok_or_else(|| DbError::FailedToGetMaxVersionByName(crate_name.to_string()))?;
2238
2239 let txn = self.db_con.begin().await?;
2245
2246 let krate = cratesio_crate::Entity::find()
2247 .filter(cratesio_crate::Column::Name.eq(&normalized_name))
2248 .one(&txn)
2249 .await?;
2250
2251 let krate = if let Some(krate) = krate {
2252 let mut krate: cratesio_crate::ActiveModel = krate.into();
2253 krate.e_tag = Set(etag.to_string());
2254 krate.last_modified = Set(last_modified.to_string());
2255 krate.max_version = Set(max_version.into_inner());
2256 krate.update(&txn).await?
2257 } else {
2258 let krate = cratesio_crate::ActiveModel {
2259 id: ActiveValue::default(),
2260 name: Set(normalized_name.to_string()),
2261 original_name: Set(crate_name.to_string()),
2262 description: Set(description),
2263 e_tag: Set(etag.to_string()),
2264 last_modified: Set(last_modified.to_string()),
2265 total_downloads: Set(0),
2266 max_version: Set(max_version.to_string()),
2267 };
2268 krate.insert(&txn).await?
2269 };
2270
2271 let current_indices = cratesio_index::Entity::find()
2272 .filter(cratesio_index::Column::CratesIoFk.eq(krate.id))
2273 .all(&txn)
2274 .await?;
2275
2276 for index in indices {
2277 if let Some(current_index) = current_indices.iter().find(|ci| index.vers == ci.vers) {
2282 let mut ci: cratesio_index::ActiveModel = current_index.to_owned().into();
2283 let mut changed = false;
2284
2285 if index.yanked != current_index.yanked {
2286 ci.yanked = Set(index.yanked);
2287 changed = true;
2288 }
2289
2290 if index.rust_version != current_index.rust_version {
2291 ci.rust_version = Set(index.rust_version.clone());
2292 changed = true;
2293 }
2294
2295 if changed {
2296 ci.update(&txn).await?;
2297 }
2298 } else {
2299 let deps = if index.deps.is_empty() {
2300 None
2301 } else {
2302 let deps = serde_json::to_value(&index.deps)
2303 .map_err(|e| DbError::FailedToConvertToJson(e.to_string()))?;
2304 Some(deps)
2305 };
2306
2307 let features = serde_json::to_value(&index.features)
2308 .map_err(|e| DbError::FailedToConvertToJson(e.to_string()))?;
2309
2310 let features2 = serde_json::to_value(&index.features2)
2311 .map_err(|e| DbError::FailedToConvertToJson(e.to_string()))?;
2312
2313 let new_index = cratesio_index::ActiveModel {
2314 id: ActiveValue::default(),
2315 name: Set(index.name.clone()),
2316 vers: Set(index.vers.clone()),
2317 deps: Set(deps),
2318 cksum: Set(index.cksum.clone()),
2319 features: Set(Some(features)),
2320 features2: Set(Some(features2)),
2321 yanked: Set(index.yanked),
2322 links: Set(index.links.clone()),
2323 pubtime: Set(index.pubtime.map(|dt| dt.naive_utc())),
2324 v: Set(index.v.unwrap_or(1) as i32),
2325 crates_io_fk: Set(krate.id),
2326 rust_version: Set(index.rust_version.clone()),
2327 };
2328
2329 new_index.insert(&txn).await?;
2330
2331 let meta = cratesio_meta::ActiveModel {
2333 id: ActiveValue::default(),
2334 version: Set(index.vers.clone()),
2335 downloads: Set(0),
2336 crates_io_fk: Set(krate.id),
2337 documentation: Set(Some(format!(
2338 "https://docs.rs/{normalized_name}/{}",
2339 index.vers,
2340 ))),
2341 license: Set(licenses.get(&index.vers).cloned()),
2342 };
2343
2344 meta.insert(&txn).await?;
2345 }
2346 }
2347
2348 let unlicensed = cratesio_meta::Entity::find()
2351 .filter(cratesio_meta::Column::CratesIoFk.eq(krate.id))
2352 .filter(cratesio_meta::Column::License.is_null())
2353 .all(&txn)
2354 .await?;
2355 for meta in unlicensed {
2356 if let Some(license) = licenses.get(&meta.version) {
2357 let mut meta: cratesio_meta::ActiveModel = meta.into();
2358 meta.license = Set(Some(license.clone()));
2359 meta.update(&txn).await?;
2360 }
2361 }
2362
2363 txn.commit().await?;
2364
2365 Ok(Prefetch {
2366 data: operations::index_metadata_to_bytes(indices)?,
2367 etag: etag.to_string(),
2368 last_modified: last_modified.to_string(),
2369 })
2370 }
2371
2372 async fn get_cratesio_index_update_list(&self) -> DbResult<Vec<CratesioPrefetchMsg>> {
2373 let crates = cratesio_crate::Entity::find().all(&self.db_con).await?;
2374 let msgs = crates
2375 .into_iter()
2376 .map(|krate| {
2377 CratesioPrefetchMsg::Update(UpdateData {
2378 name: OriginalName::from_unchecked(krate.original_name),
2379 etag: Some(krate.e_tag),
2380 last_modified: Some(krate.last_modified),
2381 })
2382 })
2383 .collect();
2384 Ok(msgs)
2385 }
2386
2387 async fn get_cratesio_version_meta(
2388 &self,
2389 crate_name: &NormalizedName,
2390 version: &Version,
2391 ) -> DbResult<Option<CratesioVersionMeta>> {
2392 #[derive(FromQueryResult)]
2393 struct Meta {
2394 cksum: String,
2395 license: Option<String>,
2396 }
2397
2398 let version = version.to_string();
2400 let meta_version = version.clone();
2401 let meta_of_version =
2402 cratesio_crate::Relation::CratesioMeta
2403 .def()
2404 .on_condition(move |_crate, meta| {
2405 Condition::all().add(
2406 Expr::col((meta, cratesio_meta::Column::Version)).eq(meta_version.clone()),
2407 )
2408 });
2409 let meta = cratesio_index::Entity::find()
2410 .select_only()
2411 .column(cratesio_index::Column::Cksum)
2412 .column(cratesio_meta::Column::License)
2413 .inner_join(cratesio_crate::Entity)
2414 .join(JoinType::LeftJoin, meta_of_version)
2415 .filter(cratesio_crate::Column::Name.eq(crate_name))
2416 .filter(cratesio_index::Column::Vers.eq(version))
2417 .into_model::<Meta>()
2418 .one(&self.db_con)
2419 .await?;
2420
2421 Ok(meta.map(|m| CratesioVersionMeta {
2422 cksum: m.cksum,
2423 license: m.license,
2424 }))
2425 }
2426
2427 async fn get_crate_license(
2428 &self,
2429 crate_name: &NormalizedName,
2430 version: &Version,
2431 ) -> DbResult<Option<String>> {
2432 let meta = crate_meta::Entity::find()
2433 .inner_join(krate::Entity)
2434 .filter(krate::Column::Name.eq(crate_name))
2435 .filter(crate_meta::Column::Version.eq(version))
2436 .one(&self.db_con)
2437 .await?;
2438 Ok(meta.and_then(|m| m.license))
2439 }
2440
2441 async fn unyank_crate(&self, crate_name: &NormalizedName, version: &Version) -> DbResult<()> {
2442 let mut ci: crate_index::ActiveModel = self
2443 .get_crate_index_model(crate_name, version)
2444 .await?
2445 .into();
2446 ci.yanked = Set(false);
2447 ci.save(&self.db_con).await?;
2448 Ok(())
2449 }
2450
2451 async fn yank_crate(&self, crate_name: &NormalizedName, version: &Version) -> DbResult<()> {
2452 let mut ci: crate_index::ActiveModel = self
2453 .get_crate_index_model(crate_name, version)
2454 .await?
2455 .into();
2456 ci.yanked = Set(true);
2457 ci.save(&self.db_con).await?;
2458 Ok(())
2459 }
2460
2461 async fn register_webhook(&self, webhook: Webhook) -> DbResult<String> {
2462 let w = webhook::ActiveModel {
2463 event: Set(Into::<&str>::into(webhook.event).to_string()),
2464 callback_url: Set(webhook.callback_url),
2465 name: Set(webhook.name),
2466 secret: Set(webhook.secret),
2467 ..Default::default()
2468 };
2469
2470 let w: webhook::Model = w.insert(&self.db_con).await?;
2471 Ok(w.id.to_string())
2472 }
2473
2474 async fn delete_webhook(&self, id: &str) -> DbResult<()> {
2475 self.get_webhook_model(id)
2476 .await?
2477 .delete(&self.db_con)
2478 .await?;
2479 Ok(())
2480 }
2481
2482 async fn get_webhook(&self, id: &str) -> DbResult<Webhook> {
2483 webhook_model_to_obj(self.get_webhook_model(id).await?)
2484 }
2485
2486 async fn get_all_webhooks(&self) -> DbResult<Vec<Webhook>> {
2487 Ok(webhook::Entity::find()
2488 .all(&self.db_con)
2489 .await?
2490 .into_iter()
2491 .filter_map(|w| webhook_model_to_obj(w).ok())
2492 .collect())
2493 }
2494
2495 async fn add_webhook_queue(
2496 &self,
2497 event: WebhookEvent,
2498 payload: serde_json::Value,
2499 ) -> DbResult<()> {
2500 let w = webhook::Entity::find()
2501 .filter(webhook::Column::Event.eq(Into::<&str>::into(event)))
2502 .all(&self.db_con)
2503 .await?;
2504
2505 if w.is_empty() {
2506 return Ok(());
2507 }
2508
2509 let now = Utc::now();
2510
2511 let entries = w.into_iter().map(|w| webhook_queue::ActiveModel {
2512 webhook_fk: Set(w.id),
2513 payload: Set(payload.clone()),
2514 next_attempt: Set(now.into()),
2515 last_attempt: Set(None),
2516 ..Default::default()
2517 });
2518
2519 webhook_queue::Entity::insert_many(entries)
2520 .exec(&self.db_con)
2521 .await?;
2522 Ok(())
2523 }
2524 async fn get_pending_webhook_queue_entries(
2525 &self,
2526 timestamp: DateTime<Utc>,
2527 ) -> DbResult<Vec<WebhookQueue>> {
2528 let w = webhook_queue::Entity::find()
2529 .find_with_related(webhook::Entity)
2530 .filter(webhook_queue::Column::NextAttempt.lte(timestamp))
2531 .all(&self.db_con)
2532 .await?;
2533
2534 Ok(w.into_iter()
2535 .filter_map(|w| {
2536 let webhook = w.1.first()?;
2537 Some(WebhookQueue {
2538 id: w.0.id.to_string(),
2539 callback_url: webhook.callback_url.clone(),
2540 payload: w.0.payload,
2541 last_attempt: w.0.last_attempt.map(Into::into),
2542 next_attempt: w.0.next_attempt.into(),
2543 secret: webhook.secret.clone(),
2544 })
2545 })
2546 .collect())
2547 }
2548
2549 async fn update_webhook_queue(
2550 &self,
2551 id: &str,
2552 last_attempt: DateTime<Utc>,
2553 next_attempt: DateTime<Utc>,
2554 ) -> DbResult<()> {
2555 let mut w: webhook_queue::ActiveModel = self.get_webhook_queue_model(id).await?.into();
2556 w.last_attempt = Set(Some(last_attempt.into()));
2557 w.next_attempt = Set(next_attempt.into());
2558 w.update(&self.db_con).await?;
2559 Ok(())
2560 }
2561
2562 async fn delete_webhook_queue(&self, id: &str) -> DbResult<()> {
2563 self.get_webhook_queue_model(id)
2564 .await?
2565 .delete(&self.db_con)
2566 .await?;
2567 Ok(())
2568 }
2569
2570 async fn get_user_by_oauth2_identity(
2573 &self,
2574 issuer: &str,
2575 subject: &str,
2576 ) -> DbResult<Option<User>> {
2577 let identity = oauth2_identity::Entity::find()
2578 .filter(oauth2_identity::Column::ProviderIssuer.eq(issuer))
2579 .filter(oauth2_identity::Column::Subject.eq(subject))
2580 .one(&self.db_con)
2581 .await?;
2582
2583 if let Some(identity) = identity {
2584 let u = user::Entity::find_by_id(identity.user_fk)
2585 .one(&self.db_con)
2586 .await?
2587 .ok_or_else(|| DbError::UserNotFound(format!("user_id={}", identity.user_fk)))?;
2588
2589 Ok(Some(User::from(u)))
2590 } else {
2591 Ok(None)
2592 }
2593 }
2594
2595 async fn has_oauth2_admin(&self) -> DbResult<bool> {
2596 let found = user::Entity::find()
2597 .filter(user::Column::IsAdmin.eq(true))
2598 .inner_join(oauth2_identity::Entity)
2599 .one(&self.db_con)
2600 .await?;
2601
2602 Ok(found.is_some())
2603 }
2604
2605 async fn create_oauth2_user(
2606 &self,
2607 username: &str,
2608 issuer: &str,
2609 subject: &str,
2610 email: Option<String>,
2611 is_admin: bool,
2612 is_read_only: bool,
2613 ) -> DbResult<User> {
2614 let random_pwd = Uuid::new_v4().to_string();
2617 let hashed_pwd = hash_pwd(&random_pwd).await;
2618 let created = Utc::now().format(DB_DATE_FORMAT).to_string();
2619
2620 let txn = self.db_con.begin().await?;
2622
2623 let new_user = user::ActiveModel {
2625 name: Set(username.to_string()),
2626 pwd: Set(hashed_pwd),
2627 salt: Set(String::new()),
2628 is_admin: Set(is_admin),
2629 is_read_only: Set(is_read_only),
2630 created: Set(created.clone()),
2631 ..Default::default()
2632 };
2633
2634 let res = user::Entity::insert(new_user).exec(&txn).await?;
2635 let user_id = res.last_insert_id;
2636
2637 let identity = oauth2_identity::ActiveModel {
2639 user_fk: Set(user_id),
2640 provider_issuer: Set(issuer.to_string()),
2641 subject: Set(subject.to_string()),
2642 email: Set(email),
2643 created: Set(created.clone()),
2644 ..Default::default()
2645 };
2646
2647 oauth2_identity::Entity::insert(identity).exec(&txn).await?;
2648
2649 txn.commit().await?;
2650
2651 Ok(User {
2652 id: user_id as i32,
2653 name: username.to_string(),
2654 pwd: String::new(), salt: String::new(), is_admin,
2657 is_read_only,
2658 created,
2659 })
2660 }
2661
2662 async fn link_oauth2_identity(
2663 &self,
2664 user_id: i64,
2665 issuer: &str,
2666 subject: &str,
2667 email: Option<String>,
2668 ) -> DbResult<()> {
2669 let created = Utc::now().format(DB_DATE_FORMAT).to_string();
2670 let identity = oauth2_identity::ActiveModel {
2671 user_fk: Set(user_id),
2672 provider_issuer: Set(issuer.to_string()),
2673 subject: Set(subject.to_string()),
2674 email: Set(email),
2675 created: Set(created),
2676 ..Default::default()
2677 };
2678
2679 oauth2_identity::Entity::insert(identity)
2680 .exec(&self.db_con)
2681 .await?;
2682
2683 Ok(())
2684 }
2685
2686 async fn store_oauth2_state(
2689 &self,
2690 state: &str,
2691 pkce_verifier: &str,
2692 nonce: &str,
2693 ) -> DbResult<()> {
2694 let created = Utc::now().format(DB_DATE_FORMAT).to_string();
2695 let s = oauth2_state::ActiveModel {
2696 state: Set(state.to_string()),
2697 pkce_verifier: Set(pkce_verifier.to_string()),
2698 nonce: Set(nonce.to_string()),
2699 created: Set(created),
2700 ..Default::default()
2701 };
2702
2703 oauth2_state::Entity::insert(s).exec(&self.db_con).await?;
2704 Ok(())
2705 }
2706
2707 async fn get_and_delete_oauth2_state(&self, state: &str) -> DbResult<Option<OAuth2StateData>> {
2708 let Some(s) = oauth2_state::Entity::find()
2709 .filter(oauth2_state::Column::State.eq(state))
2710 .one(&self.db_con)
2711 .await?
2712 else {
2713 return Ok(None);
2714 };
2715
2716 let deleted = oauth2_state::Entity::delete_many()
2720 .filter(oauth2_state::Column::Id.eq(s.id))
2721 .exec(&self.db_con)
2722 .await?
2723 .rows_affected;
2724 if deleted == 0 || s.created < oauth2_state_expiry_cutoff() {
2725 return Ok(None);
2726 }
2727
2728 Ok(Some(OAuth2StateData {
2729 state: s.state,
2730 pkce_verifier: s.pkce_verifier,
2731 nonce: s.nonce,
2732 }))
2733 }
2734
2735 async fn cleanup_expired_oauth2_states(&self) -> DbResult<u64> {
2736 let result = oauth2_state::Entity::delete_many()
2737 .filter(oauth2_state::Column::Created.lt(oauth2_state_expiry_cutoff()))
2738 .exec(&self.db_con)
2739 .await?;
2740
2741 Ok(result.rows_affected)
2742 }
2743
2744 async fn is_username_available(&self, username: &str) -> DbResult<bool> {
2745 let existing = user::Entity::find()
2746 .filter(user::Column::Name.eq(username))
2747 .one(&self.db_con)
2748 .await?;
2749
2750 Ok(existing.is_none())
2751 }
2752
2753 async fn add_toolchain(
2756 &self,
2757 name: &str,
2758 version: &str,
2759 date: &str,
2760 channel: Option<String>,
2761 ) -> DbResult<i64> {
2762 let created = Utc::now().format(DB_DATE_FORMAT).to_string();
2763
2764 let model = toolchain::ActiveModel {
2765 id: ActiveValue::NotSet,
2766 name: Set(name.to_string()),
2767 version: Set(version.to_string()),
2768 date: Set(date.to_string()),
2769 channel: Set(channel),
2770 created: Set(created),
2771 };
2772
2773 let result = model.insert(&self.db_con).await?;
2774 Ok(result.id)
2775 }
2776
2777 async fn add_toolchain_target(
2778 &self,
2779 toolchain_id: i64,
2780 target: &str,
2781 storage_path: &str,
2782 hash: &str,
2783 size: i64,
2784 ) -> DbResult<i64> {
2785 let model = toolchain_target::ActiveModel {
2786 id: ActiveValue::NotSet,
2787 toolchain_fk: Set(toolchain_id),
2788 target: Set(target.to_string()),
2789 storage_path: Set(storage_path.to_string()),
2790 hash: Set(hash.to_string()),
2791 size: Set(size),
2792 status: Set("processing".to_string()),
2793 };
2794
2795 let result = model.insert(&self.db_con).await?;
2796 Ok(result.id)
2797 }
2798
2799 async fn set_target_status(&self, target_id: i64, status: &str) -> DbResult<()> {
2800 toolchain_target::Entity::update_many()
2801 .col_expr(toolchain_target::Column::Status, Expr::value(status))
2802 .filter(toolchain_target::Column::Id.eq(target_id))
2803 .exec(&self.db_con)
2804 .await?;
2805 Ok(())
2806 }
2807
2808 async fn add_toolchain_component(
2809 &self,
2810 target_id: i64,
2811 name: &str,
2812 storage_path: &str,
2813 hash: &str,
2814 size: i64,
2815 ) -> DbResult<()> {
2816 let model = toolchain_component::ActiveModel {
2817 id: ActiveValue::NotSet,
2818 toolchain_target_fk: Set(target_id),
2819 name: Set(name.to_string()),
2820 storage_path: Set(storage_path.to_string()),
2821 hash: Set(hash.to_string()),
2822 size: Set(size),
2823 };
2824
2825 model.insert(&self.db_con).await?;
2826 Ok(())
2827 }
2828
2829 async fn get_toolchain_by_channel(
2830 &self,
2831 channel: &str,
2832 ) -> DbResult<Option<ToolchainWithTargets>> {
2833 let tc = toolchain::Entity::find()
2834 .filter(toolchain::Column::Channel.eq(channel))
2835 .one(&self.db_con)
2836 .await?;
2837
2838 Ok(match tc {
2839 Some(tc) => Some(self.toolchain_with_targets(tc).await?),
2840 None => None,
2841 })
2842 }
2843
2844 async fn get_toolchain_by_version(
2845 &self,
2846 name: &str,
2847 version: &str,
2848 ) -> DbResult<Option<ToolchainWithTargets>> {
2849 Ok(
2850 match self.get_toolchain_by_name_version(name, version).await? {
2851 Some(tc) => Some(self.toolchain_with_targets(tc).await?),
2852 None => None,
2853 },
2854 )
2855 }
2856
2857 async fn list_toolchains(&self) -> DbResult<Vec<ToolchainWithTargets>> {
2858 let toolchains = toolchain::Entity::find()
2859 .order_by_desc(toolchain::Column::Created)
2860 .all(&self.db_con)
2861 .await?;
2862
2863 let mut result = Vec::with_capacity(toolchains.len());
2864
2865 for tc in toolchains {
2866 result.push(self.toolchain_with_targets(tc).await?);
2867 }
2868
2869 Ok(result)
2870 }
2871
2872 async fn delete_toolchain(&self, name: &str, version: &str) -> DbResult<()> {
2873 toolchain::Entity::delete_many()
2874 .filter(toolchain::Column::Name.eq(name))
2875 .filter(toolchain::Column::Version.eq(version))
2876 .exec(&self.db_con)
2877 .await?;
2878
2879 Ok(())
2880 }
2881
2882 async fn delete_toolchain_target(
2883 &self,
2884 name: &str,
2885 version: &str,
2886 target: &str,
2887 ) -> DbResult<()> {
2888 if let Some(tc) = self.get_toolchain_by_name_version(name, version).await? {
2889 toolchain_target::Entity::delete_many()
2890 .filter(toolchain_target::Column::ToolchainFk.eq(tc.id))
2891 .filter(toolchain_target::Column::Target.eq(target))
2892 .exec(&self.db_con)
2893 .await?;
2894 }
2895
2896 Ok(())
2897 }
2898
2899 async fn set_channel(&self, channel: &str, name: &str, version: &str) -> DbResult<()> {
2900 let toolchains_with_channel = toolchain::Entity::find()
2902 .filter(toolchain::Column::Channel.eq(channel))
2903 .all(&self.db_con)
2904 .await?;
2905
2906 for tc in toolchains_with_channel {
2907 let mut model: toolchain::ActiveModel = tc.into();
2908 model.channel = Set(None);
2909 model.update(&self.db_con).await?;
2910 }
2911
2912 if let Some(tc) = self.get_toolchain_by_name_version(name, version).await? {
2914 let mut model: toolchain::ActiveModel = tc.into();
2915 model.channel = Set(Some(channel.to_string()));
2916 model.update(&self.db_con).await?;
2917 }
2918
2919 Ok(())
2920 }
2921
2922 async fn get_channels(&self) -> DbResult<Vec<ChannelInfo>> {
2923 let toolchains = toolchain::Entity::find()
2924 .filter(toolchain::Column::Channel.is_not_null())
2925 .all(&self.db_con)
2926 .await?;
2927
2928 Ok(toolchains
2929 .into_iter()
2930 .filter_map(|tc| {
2931 tc.channel.map(|channel| ChannelInfo {
2932 name: channel,
2933 version: tc.version,
2934 date: tc.date,
2935 })
2936 })
2937 .collect())
2938 }
2939}
2940
2941fn parse_db_version(value: &str) -> DbResult<Version> {
2942 Version::try_from(value).map_err(|_| DbError::InvalidVersion(value.to_owned()))
2943}
2944
2945#[cfg(test)]
2946mod like_escape_tests {
2947 use super::escape_like_pattern;
2948
2949 #[test]
2950 fn escapes_wildcards_and_backslash() {
2951 assert_eq!(escape_like_pattern("a%b_c"), "a\\%b\\_c");
2952 assert_eq!(escape_like_pattern("100%"), "100\\%");
2953 assert_eq!(escape_like_pattern("a\\b"), "a\\\\b");
2955 }
2956
2957 #[test]
2958 fn leaves_plain_text_untouched() {
2959 assert_eq!(escape_like_pattern("serde-json"), "serde-json");
2960 }
2961}
2962
2963#[cfg(test)]
2964mod session_age_tests {
2965 use super::{MAX_SESSION_AGE, session_age_to_duration};
2966
2967 #[test]
2968 fn zero_means_never_expire() {
2969 assert_eq!(session_age_to_duration(std::time::Duration::ZERO), None);
2970 }
2971
2972 #[test]
2973 fn positive_age_is_preserved() {
2974 assert_eq!(
2975 session_age_to_duration(std::time::Duration::from_hours(1)),
2976 Some(chrono::Duration::seconds(3600))
2977 );
2978 }
2979
2980 #[test]
2981 fn absurd_age_is_clamped_without_panicking() {
2982 let resolved = session_age_to_duration(std::time::Duration::from_secs(u64::MAX))
2984 .expect("non-zero age yields Some");
2985 assert_eq!(
2986 resolved,
2987 chrono::Duration::from_std(MAX_SESSION_AGE).unwrap()
2988 );
2989 }
2990}