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