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