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