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