Skip to main content

kellnr_db/
provider.rs

1use std::collections::HashMap;
2use std::path::Path;
3
4use chrono::{DateTime, Utc};
5use crate_meta::CrateMeta;
6use kellnr_common::account_name::{GroupName, UserName};
7use kellnr_common::canonical_name::CanonicalName;
8use kellnr_common::crate_data::CrateData;
9use kellnr_common::crate_dependents::CrateDependents;
10use kellnr_common::crate_overview::CrateOverview;
11use kellnr_common::cratesio_prefetch_msg::CratesioPrefetchMsg;
12use kellnr_common::index_metadata::IndexMetadata;
13use kellnr_common::original_name::OriginalName;
14use kellnr_common::plain_token::PlainToken;
15use kellnr_common::prefetch::Prefetch;
16use kellnr_common::publish_metadata::PublishMetadata;
17use kellnr_common::version::Version;
18use kellnr_common::webhook::{Webhook, WebhookEvent, WebhookQueue};
19use sea_orm::prelude::async_trait::async_trait;
20use serde::{Deserialize, Serialize};
21
22use crate::error::DbError;
23use crate::{
24    AuthToken, AuthTokenId, CrateId, DocQueueEntry, Group, ToolchainTargetStatus, User, UserFlags,
25    crate_meta,
26};
27
28pub type DbResult<T> = Result<T, DbError>;
29
30#[derive(Debug, PartialEq, Eq)]
31pub enum PrefetchState {
32    /// The client's cached index is current. Carries the validators stored in
33    /// the database, which are the only ones that may be sent upstream.
34    UpToDate {
35        etag: String,
36        last_modified: String,
37    },
38    NeedsUpdate(Prefetch),
39    NotFound,
40}
41
42/// Checksum and license of a cached crates.io crate version.
43#[derive(Debug, Clone, PartialEq, Eq)]
44pub struct CratesioVersionMeta {
45    pub cksum: String,
46    /// `None` if the license was not fetched yet, `UNKNOWN_LICENSE` if the
47    /// version declares none.
48    pub license: Option<String>,
49}
50
51/// Which download-restricted crates a caller may see in crate listings.
52#[derive(Debug, Clone, PartialEq, Eq)]
53pub enum CrateVisibility {
54    /// All crates, used for admins.
55    All,
56    /// Unrestricted crates and the restricted crates the user owns or may
57    /// access as crate user or crate group member.
58    User(UserName),
59    /// Unrestricted crates only, used for anonymous callers.
60    Unrestricted,
61}
62
63/// Order of the crate overview list.
64#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize, utoipa::ToSchema)]
65#[serde(rename_all = "lowercase")]
66pub enum CrateSort {
67    /// Alphabetical by name.
68    #[default]
69    Name,
70    /// Most recently updated first.
71    Updated,
72    /// Most downloaded first.
73    Downloads,
74}
75
76/// Restricts the crate overview list to crates with a keyword and/or a
77/// category. Both match case-insensitively and ignore crates.io proxy crates,
78/// which have neither.
79#[derive(Debug, Clone, Default, PartialEq, Eq)]
80pub struct CrateTagFilter {
81    pub keyword: Option<String>,
82    pub category: Option<String>,
83}
84
85impl CrateTagFilter {
86    pub fn is_empty(&self) -> bool {
87        self.keyword.is_none() && self.category.is_none()
88    }
89}
90
91/// Authenticated user and privileges resolved from a valid session cookie.
92#[derive(Debug, Clone, PartialEq, Eq)]
93pub struct SessionInfo {
94    pub name: UserName,
95    pub is_admin: bool,
96    pub is_read_only: bool,
97}
98
99/// Data stored during `OAuth2` authentication flow for CSRF/PKCE verification
100#[derive(Debug, Clone, PartialEq, Eq)]
101pub struct OAuth2StateData {
102    pub state: String,
103    pub pkce_verifier: String,
104    pub nonce: String,
105}
106
107/// Information about an individual toolchain component (e.g., rustc, cargo)
108#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, utoipa::ToSchema)]
109pub struct ToolchainComponentInfo {
110    /// Component name (e.g., "rustc", "cargo", "rust-std-x86_64-unknown-linux-gnu")
111    pub name: String,
112    /// Path to the stored component archive
113    pub storage_path: String,
114    /// SHA256 hash of the component archive
115    pub hash: String,
116    /// Component archive size in bytes
117    pub size: i64,
118}
119
120impl From<kellnr_entity::toolchain_component::Model> for ToolchainComponentInfo {
121    fn from(c: kellnr_entity::toolchain_component::Model) -> Self {
122        Self {
123            name: c.name,
124            storage_path: c.storage_path,
125            hash: c.hash,
126            size: c.size,
127        }
128    }
129}
130
131/// Toolchain target information (e.g., a specific archive for a target triple)
132#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, utoipa::ToSchema)]
133pub struct ToolchainTargetInfo {
134    /// Target ID
135    pub id: i64,
136    /// Target triple (e.g., "x86_64-unknown-linux-gnu")
137    pub target: String,
138    /// Path to the stored archive
139    pub storage_path: String,
140    /// SHA256 hash of the archive
141    pub hash: String,
142    /// Archive size in bytes
143    pub size: i64,
144    /// Processing status
145    pub status: ToolchainTargetStatus,
146    /// Individual components extracted from the combined archive
147    pub components: Vec<ToolchainComponentInfo>,
148}
149
150/// Toolchain with all its targets
151#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, utoipa::ToSchema)]
152pub struct ToolchainWithTargets {
153    /// Toolchain ID
154    pub id: i64,
155    /// Toolchain name (e.g., "rust")
156    pub name: String,
157    /// Toolchain version (e.g., "1.75.0")
158    pub version: String,
159    /// Release date
160    pub date: String,
161    /// Channel (e.g., "stable", "beta", "nightly")
162    pub channel: Option<String>,
163    /// Creation timestamp
164    pub created: String,
165    /// Available targets for this toolchain
166    pub targets: Vec<ToolchainTargetInfo>,
167}
168
169/// Channel information (e.g., "stable" -> "1.75.0")
170#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, utoipa::ToSchema)]
171pub struct ChannelInfo {
172    /// Channel name (e.g., "stable", "beta", "nightly")
173    pub name: String,
174    /// Toolchain version pointed to by this channel
175    pub version: String,
176    /// Release date
177    pub date: String,
178}
179
180#[cfg_attr(feature = "mock", mockall::automock)]
181#[async_trait]
182pub trait DbProvider: Send + Sync {
183    async fn get_last_updated_crate(
184        &self,
185        visibility: &CrateVisibility,
186    ) -> DbResult<Option<(OriginalName, Version)>>;
187    async fn authenticate_user(&self, name: &UserName, pwd: &str) -> DbResult<User>;
188    async fn increase_download_counter(
189        &self,
190        crate_name: &CanonicalName,
191        crate_version: &Version,
192    ) -> DbResult<()>;
193    async fn increase_cached_download_counter(
194        &self,
195        crate_name: &CanonicalName,
196        crate_version: &Version,
197    ) -> DbResult<()>;
198    async fn increase_download_counter_by(
199        &self,
200        crate_name: &CanonicalName,
201        crate_version: &Version,
202        count: u64,
203    ) -> DbResult<()>;
204    async fn increase_cached_download_counter_by(
205        &self,
206        crate_name: &CanonicalName,
207        crate_version: &Version,
208        count: u64,
209    ) -> DbResult<()>;
210    async fn validate_session(&self, session_token: &PlainToken) -> DbResult<SessionInfo>;
211    /// Delete all sessions that have outlived the configured session age.
212    /// Returns the number of rows removed.
213    async fn delete_expired_sessions(&self) -> DbResult<u64>;
214    async fn add_session_token(&self, name: &UserName, session_token: &PlainToken) -> DbResult<()>;
215    /// Delete all sessions of a user. Returns the number of rows removed.
216    async fn delete_user_sessions(&self, user_name: &UserName) -> DbResult<u64>;
217    /// Delete all sessions of a user except `keep_session_token`. Returns the
218    /// number of rows removed.
219    async fn delete_other_user_sessions(
220        &self,
221        user_name: &UserName,
222        keep_session_token: &PlainToken,
223    ) -> DbResult<u64>;
224    async fn add_crate_user(&self, crate_name: &CanonicalName, user: &UserName) -> DbResult<()>;
225    /// Adds `owner` to the owners of the crate. Adding an existing owner
226    /// succeeds without a change.
227    async fn add_owner(&self, crate_name: &CanonicalName, owner: &UserName) -> DbResult<()>;
228    async fn is_download_restricted(&self, crate_name: &CanonicalName) -> DbResult<bool>;
229    async fn change_download_restricted(
230        &self,
231        crate_name: &CanonicalName,
232        restricted: bool,
233    ) -> DbResult<()>;
234    async fn is_crate_user(&self, crate_name: &CanonicalName, user: &UserName) -> DbResult<bool>;
235    async fn is_owner(&self, crate_name: &CanonicalName, user: &UserName) -> DbResult<bool>;
236    async fn get_crate_id(&self, crate_name: &CanonicalName) -> DbResult<Option<CrateId>>;
237    async fn get_crate_owners(&self, crate_name: &CanonicalName) -> DbResult<Vec<User>>;
238    async fn get_crate_users(&self, crate_name: &CanonicalName) -> DbResult<Vec<User>>;
239    async fn get_crate_versions(&self, crate_name: &CanonicalName) -> DbResult<Vec<Version>>;
240    async fn delete_session_token(&self, session_token: &PlainToken) -> DbResult<()>;
241    async fn delete_user(&self, user_name: &UserName) -> DbResult<()>;
242    async fn change_pwd(&self, user_name: &UserName, new_pwd: &str) -> DbResult<()>;
243    async fn change_read_only_state(&self, user_name: &UserName, state: bool) -> DbResult<()>;
244    async fn change_admin_state(&self, user_name: &UserName, state: bool) -> DbResult<()>;
245    async fn crate_version_exists(&self, crate_id: CrateId, version: &Version) -> DbResult<bool>;
246    async fn get_max_version_from_id(&self, crate_id: CrateId) -> DbResult<Version>;
247    async fn get_max_version_from_name(&self, crate_name: &CanonicalName) -> DbResult<Version>;
248    async fn add_auth_token(&self, name: &str, token: &PlainToken, user: &UserName)
249    -> DbResult<()>;
250    async fn get_user_from_token(&self, token: &PlainToken) -> DbResult<User>;
251    /// Name of the auth token, `None` if there is no such token.
252    async fn get_auth_token_name(&self, token: &PlainToken) -> DbResult<Option<String>>;
253    async fn get_user(&self, name: &UserName) -> DbResult<User>;
254    async fn get_auth_tokens(&self, user_name: &UserName) -> DbResult<Vec<AuthToken>>;
255    /// Deletes the token `id` of the user, [`DbError::TokenNotFound`] if the
256    /// user has no such token.
257    async fn delete_auth_token(&self, user_name: &UserName, id: AuthTokenId) -> DbResult<()>;
258    /// Removes all `owners` from the crate in one transaction. Nothing is
259    /// removed if one of them is not an owner ([`DbError::OwnerNotFound`]) or
260    /// if no owner would remain and `allow_ownerless` is false
261    /// ([`DbError::LastOwner`]).
262    async fn delete_owners(
263        &self,
264        crate_name: &CanonicalName,
265        owners: &[UserName],
266        allow_ownerless: bool,
267    ) -> DbResult<()>;
268    async fn delete_crate_user(&self, crate_name: &CanonicalName, user: &UserName) -> DbResult<()>;
269    async fn add_user(&self, name: &UserName, pwd: &str, flags: UserFlags) -> DbResult<()>;
270    async fn get_users(&self) -> DbResult<Vec<User>>;
271    async fn add_group(&self, name: &GroupName) -> DbResult<()>;
272    async fn get_groups(&self) -> DbResult<Vec<Group>>;
273    async fn delete_group(&self, name: &GroupName) -> DbResult<()>;
274    async fn add_group_user(&self, group_name: &GroupName, user: &UserName) -> DbResult<()>;
275    async fn delete_group_user(&self, group_name: &GroupName, user: &UserName) -> DbResult<()>;
276    async fn get_group_users(&self, group_name: &GroupName) -> DbResult<Vec<User>>;
277    async fn is_group_user(&self, group_name: &GroupName, user: &UserName) -> DbResult<bool>;
278    async fn add_crate_group(&self, crate_name: &CanonicalName, group: &GroupName) -> DbResult<()>;
279    async fn delete_crate_group(
280        &self,
281        crate_name: &CanonicalName,
282        group: &GroupName,
283    ) -> DbResult<()>;
284    async fn get_crate_groups(&self, crate_name: &CanonicalName) -> DbResult<Vec<Group>>;
285    async fn is_crate_group(&self, crate_name: &CanonicalName, group: &GroupName)
286    -> DbResult<bool>;
287    async fn is_crate_group_user(
288        &self,
289        crate_name: &CanonicalName,
290        user: &UserName,
291    ) -> DbResult<bool>;
292    async fn get_total_unique_crates(&self) -> DbResult<u64>;
293    async fn get_total_crate_versions(&self) -> DbResult<u64>;
294    async fn get_total_downloads(&self) -> DbResult<u64>;
295    async fn get_top_crates_downloads(
296        &self,
297        top: u64,
298        visibility: &CrateVisibility,
299    ) -> DbResult<Vec<(String, u64)>>;
300    async fn get_total_unique_cached_crates(&self) -> DbResult<u64>;
301    async fn get_total_cached_crate_versions(&self) -> DbResult<u64>;
302    async fn get_total_cached_downloads(&self) -> DbResult<u64>;
303    async fn add_doc_queue(
304        &self,
305        krate: &OriginalName,
306        version: &Version,
307        path: &Path,
308    ) -> DbResult<()>;
309    async fn delete_doc_queue(&self, id: i64) -> DbResult<()>;
310    async fn get_doc_queue(&self) -> DbResult<Vec<DocQueueEntry>>;
311    async fn delete_crate(&self, krate: &CanonicalName, version: &Version) -> DbResult<()>;
312    async fn get_crate_meta_list(&self, crate_name: &CanonicalName) -> DbResult<Vec<CrateMeta>>;
313    async fn search_in_crate_name_and_description(
314        &self,
315        contains: &str,
316        limit: u64,
317        offset: u64,
318        cache: bool,
319        visibility: &CrateVisibility,
320    ) -> DbResult<Vec<CrateOverview>>;
321    async fn count_by_crate_name_and_description(
322        &self,
323        contains: &str,
324        cache: bool,
325        visibility: &CrateVisibility,
326    ) -> DbResult<u64>;
327    async fn get_crate_overview_list(
328        &self,
329        limit: u64,
330        offset: u64,
331        cache: bool,
332        visibility: &CrateVisibility,
333        sort: CrateSort,
334        tags: &CrateTagFilter,
335    ) -> DbResult<Vec<CrateOverview>>;
336    async fn count_crate_overview_list(
337        &self,
338        cache: bool,
339        visibility: &CrateVisibility,
340        tags: &CrateTagFilter,
341    ) -> DbResult<u64>;
342    async fn get_crate_data(&self, crate_name: &CanonicalName) -> DbResult<CrateData>;
343    /// Crates whose latest non-yanked version depends on the crate, with the
344    /// latest version of the crate. `restricted` is always 0, the caller
345    /// decides which dependents to hide.
346    async fn get_crate_dependents(&self, crate_name: &CanonicalName) -> DbResult<CrateDependents>;
347    async fn add_empty_crate(&self, name: &str, created: &DateTime<Utc>) -> DbResult<CrateId>;
348    /// Adds a published crate version. `owner` is the publishing user, who
349    /// becomes the owner of a new crate. `token_name` is the name of the auth
350    /// token used to publish, `None` if unknown.
351    async fn add_crate(
352        &self,
353        pub_metadata: &PublishMetadata,
354        cksum: &str,
355        size: i64,
356        created: &DateTime<Utc>,
357        owner: &UserName,
358        token_name: Option<String>,
359    ) -> DbResult<CrateId>;
360    async fn update_docs_link(
361        &self,
362        crate_name: &CanonicalName,
363        version: &Version,
364        docs_link: &str,
365    ) -> DbResult<()>;
366    async fn get_prefetch_data(&self, crate_name: &OriginalName) -> DbResult<Prefetch>;
367    async fn is_cratesio_cache_up_to_date(
368        &self,
369        crate_name: &OriginalName,
370        if_none_match: Option<String>,
371        if_modified_since: Option<String>,
372    ) -> DbResult<PrefetchState>;
373    async fn add_cratesio_prefetch_data(
374        &self,
375        crate_name: &OriginalName,
376        etag: &str,
377        last_modified: &str,
378        description: Option<String>,
379        indices: &[IndexMetadata],
380        licenses: &HashMap<String, String>,
381    ) -> DbResult<Prefetch>;
382    async fn get_cratesio_index_update_list(&self) -> DbResult<Vec<CratesioPrefetchMsg>>;
383    /// Checksum and license of a crates.io crate version from the cached
384    /// index, if the version is known.
385    async fn get_cratesio_version_meta(
386        &self,
387        crate_name: &CanonicalName,
388        version: &Version,
389    ) -> DbResult<Option<CratesioVersionMeta>>;
390    /// License of a Kellnr crate version, `None` if it declares none or the
391    /// version does not exist.
392    async fn get_crate_license(
393        &self,
394        crate_name: &CanonicalName,
395        version: &Version,
396    ) -> DbResult<Option<String>>;
397    async fn unyank_crate(&self, crate_name: &CanonicalName, version: &Version) -> DbResult<()>;
398    async fn yank_crate(&self, crate_name: &CanonicalName, version: &Version) -> DbResult<()>;
399    async fn register_webhook(&self, webhook: Webhook) -> DbResult<String>;
400    async fn delete_webhook(&self, id: &str) -> DbResult<()>;
401    async fn get_webhook(&self, id: &str) -> DbResult<Webhook>;
402    async fn get_all_webhooks(&self) -> DbResult<Vec<Webhook>>;
403    /// Creates a new webhook queue entry for each register webhook
404    /// matching the given event. `Next_attempt` is set to current time,
405    ///  in order to trigger immediate dispatch.
406    async fn add_webhook_queue(
407        &self,
408        event: WebhookEvent,
409        payload: serde_json::Value,
410    ) -> DbResult<()>;
411    /// Extracts webhook queue entries with `next_attempt` at or earlier than provided timestamp.
412    async fn get_pending_webhook_queue_entries(
413        &self,
414        timestamp: DateTime<Utc>,
415    ) -> DbResult<Vec<WebhookQueue>>;
416    async fn update_webhook_queue(
417        &self,
418        id: &str,
419        last_attempt: DateTime<Utc>,
420        next_attempt: DateTime<Utc>,
421    ) -> DbResult<()>;
422    async fn delete_webhook_queue(&self, id: &str) -> DbResult<()>;
423
424    // `OAuth2` identity methods
425    /// Look up a user by their `OAuth2` identity (issuer + subject)
426    async fn get_user_by_oauth2_identity(
427        &self,
428        issuer: &str,
429        subject: &str,
430    ) -> DbResult<Option<User>>;
431
432    /// Whether any admin user has an `OAuth2` identity linked.
433    ///
434    /// Used at startup to tell a usable SSO-only deployment apart from one that
435    /// has locked itself out: the default local admin always exists, so the
436    /// presence of an admin alone says nothing.
437    async fn has_oauth2_admin(&self) -> DbResult<bool>;
438
439    /// Create a new user from `OAuth2` authentication and link their identity
440    async fn create_oauth2_user(
441        &self,
442        username: &UserName,
443        issuer: &str,
444        subject: &str,
445        email: Option<String>,
446        flags: UserFlags,
447    ) -> DbResult<User>;
448
449    // `OAuth2` state methods (CSRF/PKCE during auth flow)
450    /// Store `OAuth2` state for CSRF/PKCE verification during auth flow
451    async fn store_oauth2_state(
452        &self,
453        state: &str,
454        pkce_verifier: &str,
455        nonce: &str,
456    ) -> DbResult<()>;
457
458    /// Retrieve and delete `OAuth2` state (single use)
459    async fn get_and_delete_oauth2_state(&self, state: &str) -> DbResult<Option<OAuth2StateData>>;
460
461    /// Clean up expired `OAuth2` states (older than 10 minutes)
462    async fn cleanup_expired_oauth2_states(&self) -> DbResult<u64>;
463
464    /// Check if username is available (for `OAuth2` auto-provisioning)
465    async fn is_username_available(&self, username: &UserName) -> DbResult<bool>;
466
467    // Toolchain distribution methods
468    /// Add a new toolchain release
469    async fn add_toolchain(
470        &self,
471        name: &str,
472        version: &str,
473        date: &str,
474        channel: Option<String>,
475    ) -> DbResult<i64>;
476
477    /// Add a target archive to an existing toolchain
478    async fn add_toolchain_target(
479        &self,
480        toolchain_id: i64,
481        target: &str,
482        storage_path: &str,
483        hash: &str,
484        size: i64,
485    ) -> DbResult<i64>;
486
487    /// Add a component to a toolchain target
488    async fn add_toolchain_component(
489        &self,
490        target_id: i64,
491        name: &str,
492        storage_path: &str,
493        hash: &str,
494        size: i64,
495    ) -> DbResult<()>;
496
497    /// Whether a component of any toolchain target is stored at `storage_path`
498    async fn is_toolchain_component_stored_at(&self, storage_path: &str) -> DbResult<bool>;
499
500    /// Update the processing status of a toolchain target
501    async fn set_target_status(
502        &self,
503        target_id: i64,
504        status: ToolchainTargetStatus,
505    ) -> DbResult<()>;
506
507    /// Mark all toolchain targets that are still processing as failed.
508    /// Returns the number of marked targets.
509    async fn fail_processing_toolchain_targets(&self) -> DbResult<u64>;
510
511    /// Get a toolchain by its channel (e.g., "stable", "nightly")
512    async fn get_toolchain_by_channel(
513        &self,
514        channel: &str,
515    ) -> DbResult<Option<ToolchainWithTargets>>;
516
517    /// Get a toolchain by name and version
518    async fn get_toolchain_by_version(
519        &self,
520        name: &str,
521        version: &str,
522    ) -> DbResult<Option<ToolchainWithTargets>>;
523
524    /// List all toolchains
525    async fn list_toolchains(&self) -> DbResult<Vec<ToolchainWithTargets>>;
526
527    /// Delete a toolchain and all its targets
528    async fn delete_toolchain(&self, name: &str, version: &str) -> DbResult<()>;
529
530    /// Delete a specific toolchain target
531    async fn delete_toolchain_target(
532        &self,
533        name: &str,
534        version: &str,
535        target: &str,
536    ) -> DbResult<()>;
537
538    /// Set a channel to point to a specific toolchain version
539    async fn set_channel(&self, channel: &str, name: &str, version: &str) -> DbResult<()>;
540
541    /// Get all channels with their current versions
542    async fn get_channels(&self) -> DbResult<Vec<ChannelInfo>>;
543
544    /// The name a crate was first published with, or `None` if the crate
545    /// does not exist. Crate files are stored under this name.
546    async fn get_crate_original_name(
547        &self,
548        crate_name: &CanonicalName,
549    ) -> DbResult<Option<OriginalName>>;
550
551    /// The original name of the crate if the crate has the version.
552    async fn get_original_name(
553        &self,
554        crate_name: &CanonicalName,
555        version: &Version,
556    ) -> DbResult<Option<OriginalName>>;
557}
558
559#[cfg(feature = "mock")]
560pub mod mock {
561    pub use super::MockDbProvider as MockDb;
562}