1mod acl;
2mod auth_tokens;
3mod crates;
4mod cratesio_cache;
5mod docs;
6mod groups;
7mod oauth2;
8mod operations;
9mod search;
10mod sessions;
11mod stats;
12#[cfg(feature = "test-support")]
13pub mod test_utils;
14mod toolchains;
15mod users;
16mod webhooks;
17
18use std::collections::HashMap;
19use std::path::Path;
20
21use chrono::{DateTime, Utc};
22use kellnr_common::account_name::{GroupName, UserName};
23use kellnr_common::canonical_name::CanonicalName;
24use kellnr_common::crate_data::CrateData;
25use kellnr_common::crate_dependents::CrateDependents;
26use kellnr_common::crate_overview::CrateOverview;
27use kellnr_common::cratesio_prefetch_msg::CratesioPrefetchMsg;
28use kellnr_common::index_metadata::IndexMetadata;
29use kellnr_common::original_name::OriginalName;
30use kellnr_common::plain_token::PlainToken;
31use kellnr_common::prefetch::Prefetch;
32use kellnr_common::publish_metadata::PublishMetadata;
33use kellnr_common::version::Version;
34use kellnr_common::webhook::{Webhook, WebhookEvent, WebhookQueue};
35use kellnr_entity::{group, krate, user};
36use sea_orm::prelude::async_trait::async_trait;
37use sea_orm::query::TransactionTrait;
38use sea_orm::sea_query::{Alias, Expr, Func, Iden, Query};
39use sea_orm::{
40 ActiveModelTrait, ColumnTrait, DatabaseConnection, DatabaseTransaction, EntityTrait, ExprTrait,
41 FromQueryResult, ModelTrait, QueryFilter, Statement,
42};
43use tracing::warn;
44
45use crate::error::DbError;
46use crate::provider::{
47 ChannelInfo, CrateSort, CrateTagFilter, CrateVisibility, CratesioVersionMeta, DbResult,
48 OAuth2StateData, PrefetchState, SessionInfo, ToolchainWithTargets,
49};
50use crate::tables::init_database;
51use crate::{
52 AuthToken, AuthTokenId, ConString, CrateId, CrateMeta, DbProvider, DocQueueEntry, Group,
53 ToolchainTargetStatus, User, UserFlags,
54};
55
56const DB_DATE_FORMAT: &str = "%Y-%m-%d %H:%M:%S";
57
58use kellnr_settings::SessionAge;
59
60fn session_age_to_duration(age: SessionAge) -> Option<chrono::Duration> {
63 age.expiry()
66 .map(|d| chrono::Duration::from_std(d).expect("bounded session age fits chrono::Duration"))
67}
68
69#[derive(Debug, PartialEq, FromQueryResult)]
70struct CountResult {
71 count: Option<i64>,
72}
73
74pub struct Database {
75 db_con: DatabaseConnection,
76 session_age: Option<chrono::Duration>,
79}
80
81impl Database {
82 pub async fn new(con: &ConString, max_con: u32) -> Result<Self, DbError> {
83 let db_con = init_database(con, max_con)
84 .await
85 .map_err(|e| DbError::InitializationError(e.to_string()))?;
86
87 if operations::no_user_exists(&db_con).await? {
88 operations::insert_admin_credentials(&db_con, con).await?;
89 }
90
91 Ok(Self {
92 db_con,
93 session_age: session_age_to_duration(con.session_age()),
94 })
95 }
96
97 async fn in_transaction<T>(
102 &self,
103 f: impl AsyncFnOnce(&DatabaseTransaction) -> DbResult<T>,
104 ) -> DbResult<T> {
105 let txn = self.db_con.begin().await?;
106 match f(&txn).await {
107 Ok(value) => {
108 txn.commit().await?;
109 Ok(value)
110 }
111 Err(e) => {
112 if let Err(rollback) = txn.rollback().await {
113 warn!("Failed to roll back transaction: {rollback}");
114 }
115 Err(e)
116 }
117 }
118 }
119
120 async fn get_user_model(&self, name: &UserName) -> 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_string()))
127 }
128
129 async fn get_krate_model(&self, crate_name: &CanonicalName) -> 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: &GroupName) -> 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_string()))
145 }
146
147 async fn count<T>(&self, table: T, id_column: T, error: DbError) -> DbResult<u64>
149 where
150 T: Iden + Copy,
151 {
152 let statement = self.db_con.get_database_backend().build(
153 Query::select()
154 .expr_as(Expr::col((table, id_column)).count(), Alias::new("count"))
155 .from(table),
156 );
157 self.count_by_statement(statement, error).await
158 }
159
160 async fn sum<T>(&self, table: T, column: T, error: DbError) -> DbResult<u64>
163 where
164 T: Iden + Copy + 'static,
165 {
166 let sum = Func::coalesce([Expr::col((table, column)).sum(), Expr::val(0)]);
167 let statement = self.db_con.get_database_backend().build(
168 Query::select()
169 .expr_as(
170 Func::cast_as(sum, Alias::new("BIGINT")),
171 Alias::new("count"),
172 )
173 .from(table),
174 );
175 self.count_by_statement(statement, error).await
176 }
177
178 async fn count_by_statement(&self, statement: Statement, error: DbError) -> DbResult<u64> {
180 let Some(result) = CountResult::find_by_statement(statement)
181 .one(&self.db_con)
182 .await?
183 else {
184 return Err(error);
185 };
186 result
187 .count
188 .and_then(|v| u64::try_from(v).ok())
189 .ok_or(error)
190 }
191
192 async fn delete_or_not_found<M, A>(&self, model: Option<M>, err: DbError) -> DbResult<()>
193 where
194 M: ModelTrait + sea_orm::IntoActiveModel<A>,
195 A: ActiveModelTrait<Entity = M::Entity> + sea_orm::ActiveModelBehavior + Send,
196 {
197 model.ok_or(err)?.delete(&self.db_con).await?;
198 Ok(())
199 }
200}
201
202#[async_trait]
205impl DbProvider for Database {
206 async fn get_total_unique_cached_crates(&self) -> DbResult<u64> {
207 Database::get_total_unique_cached_crates(self).await
208 }
209
210 async fn get_total_cached_crate_versions(&self) -> DbResult<u64> {
211 Database::get_total_cached_crate_versions(self).await
212 }
213
214 async fn get_total_cached_downloads(&self) -> DbResult<u64> {
215 Database::get_total_cached_downloads(self).await
216 }
217
218 async fn authenticate_user(&self, name: &UserName, pwd: &str) -> DbResult<User> {
219 Database::authenticate_user(self, name, pwd).await
220 }
221
222 async fn increase_download_counter(
223 &self,
224 crate_name: &CanonicalName,
225 crate_version: &Version,
226 ) -> DbResult<()> {
227 Database::increase_download_counter(self, crate_name, crate_version).await
228 }
229
230 async fn increase_cached_download_counter(
231 &self,
232 crate_name: &CanonicalName,
233 crate_version: &Version,
234 ) -> DbResult<()> {
235 Database::increase_cached_download_counter(self, crate_name, crate_version).await
236 }
237
238 async fn increase_download_counter_by(
239 &self,
240 crate_name: &CanonicalName,
241 crate_version: &Version,
242 count: u64,
243 ) -> DbResult<()> {
244 Database::increase_download_counter_by(self, crate_name, crate_version, count).await
245 }
246
247 async fn increase_cached_download_counter_by(
248 &self,
249 crate_name: &CanonicalName,
250 crate_version: &Version,
251 count: u64,
252 ) -> DbResult<()> {
253 Database::increase_cached_download_counter_by(self, crate_name, crate_version, count).await
254 }
255
256 async fn get_last_updated_crate(
257 &self,
258 visibility: &CrateVisibility,
259 ) -> DbResult<Option<(OriginalName, Version)>> {
260 Database::get_last_updated_crate(self, visibility).await
261 }
262
263 async fn validate_session(&self, session_token: &PlainToken) -> DbResult<SessionInfo> {
264 Database::validate_session(self, session_token).await
265 }
266
267 async fn delete_expired_sessions(&self) -> DbResult<u64> {
268 Database::delete_expired_sessions(self).await
269 }
270
271 async fn add_session_token(&self, name: &UserName, session_token: &PlainToken) -> DbResult<()> {
272 Database::add_session_token(self, name, session_token).await
273 }
274
275 async fn delete_user_sessions(&self, user_name: &UserName) -> DbResult<u64> {
276 Database::delete_user_sessions(self, user_name).await
277 }
278
279 async fn delete_other_user_sessions(
280 &self,
281 user_name: &UserName,
282 keep_session_token: &PlainToken,
283 ) -> DbResult<u64> {
284 Database::delete_other_user_sessions(self, user_name, keep_session_token).await
285 }
286
287 async fn add_crate_user(&self, crate_name: &CanonicalName, user: &UserName) -> DbResult<()> {
288 Database::add_crate_user(self, crate_name, user).await
289 }
290
291 async fn add_crate_group(&self, crate_name: &CanonicalName, group: &GroupName) -> DbResult<()> {
292 Database::add_crate_group(self, crate_name, group).await
293 }
294
295 async fn add_group_user(&self, group_name: &GroupName, user: &UserName) -> DbResult<()> {
296 Database::add_group_user(self, group_name, user).await
297 }
298
299 async fn add_owner(&self, crate_name: &CanonicalName, owner: &UserName) -> DbResult<()> {
300 Database::add_owner(self, crate_name, owner).await
301 }
302
303 async fn is_download_restricted(&self, crate_name: &CanonicalName) -> DbResult<bool> {
304 Database::is_download_restricted(self, crate_name).await
305 }
306
307 async fn change_download_restricted(
308 &self,
309 crate_name: &CanonicalName,
310 restricted: bool,
311 ) -> DbResult<()> {
312 Database::change_download_restricted(self, crate_name, restricted).await
313 }
314
315 async fn is_crate_user(&self, crate_name: &CanonicalName, user: &UserName) -> DbResult<bool> {
316 Database::is_crate_user(self, crate_name, user).await
317 }
318
319 async fn is_crate_group(
320 &self,
321 crate_name: &CanonicalName,
322 group: &GroupName,
323 ) -> DbResult<bool> {
324 Database::is_crate_group(self, crate_name, group).await
325 }
326
327 async fn is_crate_group_user(
328 &self,
329 crate_name: &CanonicalName,
330 user: &UserName,
331 ) -> DbResult<bool> {
332 Database::is_crate_group_user(self, crate_name, user).await
333 }
334
335 async fn is_group_user(&self, group_name: &GroupName, user: &UserName) -> DbResult<bool> {
336 Database::is_group_user(self, group_name, user).await
337 }
338
339 async fn is_owner(&self, crate_name: &CanonicalName, user: &UserName) -> DbResult<bool> {
340 Database::is_owner(self, crate_name, user).await
341 }
342
343 async fn get_crate_id(&self, crate_name: &CanonicalName) -> DbResult<Option<CrateId>> {
344 Database::get_crate_id(self, crate_name).await
345 }
346
347 async fn get_crate_owners(&self, crate_name: &CanonicalName) -> DbResult<Vec<User>> {
348 Database::get_crate_owners(self, crate_name).await
349 }
350
351 async fn get_crate_users(&self, crate_name: &CanonicalName) -> DbResult<Vec<User>> {
352 Database::get_crate_users(self, crate_name).await
353 }
354
355 async fn get_crate_groups(&self, crate_name: &CanonicalName) -> DbResult<Vec<Group>> {
356 Database::get_crate_groups(self, crate_name).await
357 }
358
359 async fn get_group_users(&self, group_name: &GroupName) -> DbResult<Vec<User>> {
360 Database::get_group_users(self, group_name).await
361 }
362
363 async fn get_crate_versions(&self, crate_name: &CanonicalName) -> DbResult<Vec<Version>> {
364 Database::get_crate_versions(self, crate_name).await
365 }
366
367 async fn delete_session_token(&self, session_token: &PlainToken) -> DbResult<()> {
368 Database::delete_session_token(self, session_token).await
369 }
370
371 async fn delete_user(&self, user_name: &UserName) -> DbResult<()> {
372 Database::delete_user(self, user_name).await
373 }
374
375 async fn delete_group(&self, group_name: &GroupName) -> DbResult<()> {
376 Database::delete_group(self, group_name).await
377 }
378
379 async fn change_pwd(&self, user_name: &UserName, new_pwd: &str) -> DbResult<()> {
380 Database::change_pwd(self, user_name, new_pwd).await
381 }
382
383 async fn change_read_only_state(&self, user_name: &UserName, state: bool) -> DbResult<()> {
384 Database::change_read_only_state(self, user_name, state).await
385 }
386
387 async fn change_admin_state(&self, user_name: &UserName, state: bool) -> DbResult<()> {
388 Database::change_admin_state(self, user_name, state).await
389 }
390
391 async fn crate_version_exists(&self, crate_id: CrateId, version: &Version) -> DbResult<bool> {
392 Database::crate_version_exists(self, crate_id, version).await
393 }
394
395 async fn get_max_version_from_id(&self, crate_id: CrateId) -> DbResult<Version> {
396 Database::get_max_version_from_id(self, crate_id).await
397 }
398
399 async fn get_max_version_from_name(&self, crate_name: &CanonicalName) -> DbResult<Version> {
400 Database::get_max_version_from_name(self, crate_name).await
401 }
402
403 async fn add_auth_token(
404 &self,
405 name: &str,
406 token: &PlainToken,
407 user: &UserName,
408 ) -> DbResult<()> {
409 Database::add_auth_token(self, name, token, user).await
410 }
411
412 async fn get_user_from_token(&self, token: &PlainToken) -> DbResult<User> {
413 Database::get_user_from_token(self, token).await
414 }
415
416 async fn get_user(&self, name: &UserName) -> DbResult<User> {
417 Database::get_user(self, name).await
418 }
419
420 async fn get_auth_token_name(&self, token: &PlainToken) -> DbResult<Option<String>> {
421 Database::get_auth_token_name(self, token).await
422 }
423
424 async fn get_auth_tokens(&self, user_name: &UserName) -> DbResult<Vec<AuthToken>> {
425 Database::get_auth_tokens(self, user_name).await
426 }
427
428 async fn delete_auth_token(&self, user_name: &UserName, id: AuthTokenId) -> DbResult<()> {
429 Database::delete_auth_token(self, user_name, id).await
430 }
431
432 async fn delete_owners(
433 &self,
434 crate_name: &CanonicalName,
435 owners: &[UserName],
436 allow_ownerless: bool,
437 ) -> DbResult<()> {
438 Database::delete_owners(self, crate_name, owners, allow_ownerless).await
439 }
440
441 async fn delete_crate_user(&self, crate_name: &CanonicalName, user: &UserName) -> DbResult<()> {
442 Database::delete_crate_user(self, crate_name, user).await
443 }
444
445 async fn delete_crate_group(
446 &self,
447 crate_name: &CanonicalName,
448 group: &GroupName,
449 ) -> DbResult<()> {
450 Database::delete_crate_group(self, crate_name, group).await
451 }
452
453 async fn delete_group_user(&self, group_name: &GroupName, user: &UserName) -> DbResult<()> {
454 Database::delete_group_user(self, group_name, user).await
455 }
456
457 async fn add_user(&self, name: &UserName, pwd: &str, flags: UserFlags) -> DbResult<()> {
458 Database::add_user(self, name, pwd, flags).await
459 }
460
461 async fn add_group(&self, name: &GroupName) -> DbResult<()> {
462 Database::add_group(self, name).await
463 }
464
465 async fn get_users(&self) -> DbResult<Vec<User>> {
466 Database::get_users(self).await
467 }
468
469 async fn get_groups(&self) -> DbResult<Vec<Group>> {
470 Database::get_groups(self).await
471 }
472
473 async fn get_total_unique_crates(&self) -> DbResult<u64> {
474 Database::get_total_unique_crates(self).await
475 }
476
477 async fn get_total_crate_versions(&self) -> DbResult<u64> {
478 Database::get_total_crate_versions(self).await
479 }
480
481 async fn get_total_downloads(&self) -> DbResult<u64> {
482 Database::get_total_downloads(self).await
483 }
484
485 async fn get_top_crates_downloads(
486 &self,
487 top: u64,
488 visibility: &CrateVisibility,
489 ) -> DbResult<Vec<(String, u64)>> {
490 Database::get_top_crates_downloads(self, top, visibility).await
491 }
492
493 async fn add_doc_queue(
494 &self,
495 krate: &OriginalName,
496 version: &Version,
497 path: &Path,
498 ) -> DbResult<()> {
499 Database::add_doc_queue(self, krate, version, path).await
500 }
501
502 async fn delete_doc_queue(&self, id: i64) -> DbResult<()> {
503 Database::delete_doc_queue(self, id).await
504 }
505
506 async fn get_doc_queue(&self) -> DbResult<Vec<DocQueueEntry>> {
507 Database::get_doc_queue(self).await
508 }
509
510 async fn delete_crate(&self, krate: &CanonicalName, version: &Version) -> DbResult<()> {
511 Database::delete_crate(self, krate, version).await
512 }
513
514 async fn get_crate_meta_list(&self, crate_name: &CanonicalName) -> DbResult<Vec<CrateMeta>> {
515 Database::get_crate_meta_list(self, crate_name).await
516 }
517
518 async fn search_in_crate_name_and_description(
519 &self,
520 contains: &str,
521 limit: u64,
522 offset: u64,
523 cache: bool,
524 visibility: &CrateVisibility,
525 ) -> DbResult<Vec<CrateOverview>> {
526 Database::search_in_crate_name_and_description(
527 self, contains, limit, offset, cache, visibility,
528 )
529 .await
530 }
531
532 async fn count_by_crate_name_and_description(
533 &self,
534 contains: &str,
535 cache: bool,
536 visibility: &CrateVisibility,
537 ) -> DbResult<u64> {
538 Database::count_by_crate_name_and_description(self, contains, cache, visibility).await
539 }
540
541 async fn get_crate_overview_list(
542 &self,
543 limit: u64,
544 offset: u64,
545 cache: bool,
546 visibility: &CrateVisibility,
547 sort: CrateSort,
548 tags: &CrateTagFilter,
549 ) -> DbResult<Vec<CrateOverview>> {
550 Database::get_crate_overview_list(self, limit, offset, cache, visibility, sort, tags).await
551 }
552
553 async fn count_crate_overview_list(
554 &self,
555 cache: bool,
556 visibility: &CrateVisibility,
557 tags: &CrateTagFilter,
558 ) -> DbResult<u64> {
559 Database::count_crate_overview_list(self, cache, visibility, tags).await
560 }
561
562 async fn get_crate_data(&self, crate_name: &CanonicalName) -> DbResult<CrateData> {
563 Database::get_crate_data(self, crate_name).await
564 }
565
566 async fn get_crate_dependents(&self, crate_name: &CanonicalName) -> DbResult<CrateDependents> {
567 Database::get_crate_dependents(self, crate_name).await
568 }
569
570 async fn add_empty_crate(&self, name: &str, created: &DateTime<Utc>) -> DbResult<CrateId> {
571 Database::add_empty_crate(self, name, created).await
572 }
573
574 async fn add_crate(
575 &self,
576 pub_metadata: &PublishMetadata,
577 cksum: &str,
578 size: i64,
579 created: &DateTime<Utc>,
580 owner: &UserName,
581 token_name: Option<String>,
582 ) -> DbResult<CrateId> {
583 Database::add_crate(self, pub_metadata, cksum, size, created, owner, token_name).await
584 }
585
586 async fn update_docs_link(
587 &self,
588 crate_name: &CanonicalName,
589 version: &Version,
590 docs_link: &str,
591 ) -> DbResult<()> {
592 Database::update_docs_link(self, crate_name, version, docs_link).await
593 }
594
595 async fn get_prefetch_data(&self, crate_name: &OriginalName) -> DbResult<Prefetch> {
596 Database::get_prefetch_data(self, crate_name).await
597 }
598
599 async fn is_cratesio_cache_up_to_date(
600 &self,
601 crate_name: &OriginalName,
602 etag: Option<String>,
603 last_modified: Option<String>,
604 ) -> DbResult<PrefetchState> {
605 Database::is_cratesio_cache_up_to_date(self, crate_name, etag, last_modified).await
606 }
607
608 async fn add_cratesio_prefetch_data(
609 &self,
610 crate_name: &OriginalName,
611 etag: &str,
612 last_modified: &str,
613 description: Option<String>,
614 indices: &[IndexMetadata],
615 licenses: &HashMap<String, String>,
616 ) -> DbResult<Prefetch> {
617 Database::add_cratesio_prefetch_data(
618 self,
619 crate_name,
620 etag,
621 last_modified,
622 description,
623 indices,
624 licenses,
625 )
626 .await
627 }
628
629 async fn get_cratesio_index_update_list(&self) -> DbResult<Vec<CratesioPrefetchMsg>> {
630 Database::get_cratesio_index_update_list(self).await
631 }
632
633 async fn get_cratesio_version_meta(
634 &self,
635 crate_name: &CanonicalName,
636 version: &Version,
637 ) -> DbResult<Option<CratesioVersionMeta>> {
638 Database::get_cratesio_version_meta(self, crate_name, version).await
639 }
640
641 async fn get_crate_license(
642 &self,
643 crate_name: &CanonicalName,
644 version: &Version,
645 ) -> DbResult<Option<String>> {
646 Database::get_crate_license(self, crate_name, version).await
647 }
648
649 async fn unyank_crate(&self, crate_name: &CanonicalName, version: &Version) -> DbResult<()> {
650 Database::unyank_crate(self, crate_name, version).await
651 }
652
653 async fn yank_crate(&self, crate_name: &CanonicalName, version: &Version) -> DbResult<()> {
654 Database::yank_crate(self, crate_name, version).await
655 }
656
657 async fn register_webhook(&self, webhook: Webhook) -> DbResult<String> {
658 Database::register_webhook(self, webhook).await
659 }
660
661 async fn delete_webhook(&self, id: &str) -> DbResult<()> {
662 Database::delete_webhook(self, id).await
663 }
664
665 async fn get_webhook(&self, id: &str) -> DbResult<Webhook> {
666 Database::get_webhook(self, id).await
667 }
668
669 async fn get_all_webhooks(&self) -> DbResult<Vec<Webhook>> {
670 Database::get_all_webhooks(self).await
671 }
672
673 async fn add_webhook_queue(
674 &self,
675 event: WebhookEvent,
676 payload: serde_json::Value,
677 ) -> DbResult<()> {
678 Database::add_webhook_queue(self, event, payload).await
679 }
680
681 async fn get_pending_webhook_queue_entries(
682 &self,
683 timestamp: DateTime<Utc>,
684 ) -> DbResult<Vec<WebhookQueue>> {
685 Database::get_pending_webhook_queue_entries(self, timestamp).await
686 }
687
688 async fn update_webhook_queue(
689 &self,
690 id: &str,
691 last_attempt: DateTime<Utc>,
692 next_attempt: DateTime<Utc>,
693 ) -> DbResult<()> {
694 Database::update_webhook_queue(self, id, last_attempt, next_attempt).await
695 }
696
697 async fn delete_webhook_queue(&self, id: &str) -> DbResult<()> {
698 Database::delete_webhook_queue(self, id).await
699 }
700
701 async fn get_user_by_oauth2_identity(
702 &self,
703 issuer: &str,
704 subject: &str,
705 ) -> DbResult<Option<User>> {
706 Database::get_user_by_oauth2_identity(self, issuer, subject).await
707 }
708
709 async fn has_oauth2_admin(&self) -> DbResult<bool> {
710 Database::has_oauth2_admin(self).await
711 }
712
713 async fn create_oauth2_user(
714 &self,
715 username: &UserName,
716 issuer: &str,
717 subject: &str,
718 email: Option<String>,
719 flags: UserFlags,
720 ) -> DbResult<User> {
721 Database::create_oauth2_user(self, username, issuer, subject, email, flags).await
722 }
723
724 async fn store_oauth2_state(
725 &self,
726 state: &str,
727 pkce_verifier: &str,
728 nonce: &str,
729 ) -> DbResult<()> {
730 Database::store_oauth2_state(self, state, pkce_verifier, nonce).await
731 }
732
733 async fn get_and_delete_oauth2_state(&self, state: &str) -> DbResult<Option<OAuth2StateData>> {
734 Database::get_and_delete_oauth2_state(self, state).await
735 }
736
737 async fn cleanup_expired_oauth2_states(&self) -> DbResult<u64> {
738 Database::cleanup_expired_oauth2_states(self).await
739 }
740
741 async fn is_username_available(&self, username: &UserName) -> DbResult<bool> {
742 Database::is_username_available(self, username).await
743 }
744
745 async fn add_toolchain(
746 &self,
747 name: &str,
748 version: &str,
749 date: &str,
750 channel: Option<String>,
751 ) -> DbResult<i64> {
752 Database::add_toolchain(self, name, version, date, channel).await
753 }
754
755 async fn add_toolchain_target(
756 &self,
757 toolchain_id: i64,
758 target: &str,
759 storage_path: &str,
760 hash: &str,
761 size: i64,
762 ) -> DbResult<i64> {
763 Database::add_toolchain_target(self, toolchain_id, target, storage_path, hash, size).await
764 }
765
766 async fn set_target_status(
767 &self,
768 target_id: i64,
769 status: ToolchainTargetStatus,
770 ) -> DbResult<()> {
771 Database::set_target_status(self, target_id, status).await
772 }
773
774 async fn fail_processing_toolchain_targets(&self) -> DbResult<u64> {
775 Database::fail_processing_toolchain_targets(self).await
776 }
777
778 async fn add_toolchain_component(
779 &self,
780 target_id: i64,
781 name: &str,
782 storage_path: &str,
783 hash: &str,
784 size: i64,
785 ) -> DbResult<()> {
786 Database::add_toolchain_component(self, target_id, name, storage_path, hash, size).await
787 }
788
789 async fn is_toolchain_component_stored_at(&self, storage_path: &str) -> DbResult<bool> {
790 Database::is_toolchain_component_stored_at(self, storage_path).await
791 }
792
793 async fn get_toolchain_by_channel(
794 &self,
795 channel: &str,
796 ) -> DbResult<Option<ToolchainWithTargets>> {
797 Database::get_toolchain_by_channel(self, channel).await
798 }
799
800 async fn get_toolchain_by_version(
801 &self,
802 name: &str,
803 version: &str,
804 ) -> DbResult<Option<ToolchainWithTargets>> {
805 Database::get_toolchain_by_version(self, name, version).await
806 }
807
808 async fn list_toolchains(&self) -> DbResult<Vec<ToolchainWithTargets>> {
809 Database::list_toolchains(self).await
810 }
811
812 async fn delete_toolchain(&self, name: &str, version: &str) -> DbResult<()> {
813 Database::delete_toolchain(self, name, version).await
814 }
815
816 async fn delete_toolchain_target(
817 &self,
818 name: &str,
819 version: &str,
820 target: &str,
821 ) -> DbResult<()> {
822 Database::delete_toolchain_target(self, name, version, target).await
823 }
824
825 async fn set_channel(&self, channel: &str, name: &str, version: &str) -> DbResult<()> {
826 Database::set_channel(self, channel, name, version).await
827 }
828
829 async fn get_channels(&self) -> DbResult<Vec<ChannelInfo>> {
830 Database::get_channels(self).await
831 }
832
833 async fn get_crate_original_name(
834 &self,
835 crate_name: &CanonicalName,
836 ) -> DbResult<Option<OriginalName>> {
837 Database::get_crate_original_name(self, crate_name).await
838 }
839
840 async fn get_original_name(
841 &self,
842 crate_name: &CanonicalName,
843 version: &Version,
844 ) -> DbResult<Option<OriginalName>> {
845 Database::get_original_name(self, crate_name, version).await
846 }
847}
848
849fn saturating_i64(count: u64) -> i64 {
852 i64::try_from(count).unwrap_or(i64::MAX)
853}
854
855fn valid_version(value: &str, crate_name: &str) -> Option<Version> {
858 Version::try_from(value)
859 .inspect_err(|_| warn!("Skipping invalid version {value} of crate {crate_name}"))
860 .ok()
861}
862
863fn parse_db_version(value: &str) -> DbResult<Version> {
864 Version::try_from(value).map_err(|_| DbError::InvalidVersion(value.to_owned()))
865}
866
867#[cfg(test)]
868mod session_age_tests {
869 use kellnr_settings::constants::MAX_SESSION_AGE;
870
871 use super::{SessionAge, session_age_to_duration};
872
873 #[test]
874 fn zero_means_never_expire() {
875 assert_eq!(session_age_to_duration(SessionAge::from_secs(0)), None);
876 }
877
878 #[test]
879 fn positive_age_is_preserved() {
880 assert_eq!(
881 session_age_to_duration(SessionAge::from_secs(3600)),
882 Some(chrono::Duration::seconds(3600))
883 );
884 }
885
886 #[test]
887 fn absurd_age_is_clamped_without_panicking() {
888 let resolved = session_age_to_duration(SessionAge::from_secs(u64::MAX))
890 .expect("non-zero age yields Some");
891 assert_eq!(
892 resolved,
893 chrono::Duration::from_std(MAX_SESSION_AGE).unwrap()
894 );
895 }
896}