// This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at https://mozilla.org/MPL/2.0/. //! In-memory representations of the persisted entities. //! //! These are plain data types with no behaviour and no I/O — [`crate::store`] //! (the `store` crate) is responsible for reading and writing them. Timestamps //! are Unix milliseconds UTC ([`Timestamp`]); identifiers are ULID strings. //! //! Storage-only columns (the normalized `*_lower` uniqueness keys) are //! deliberately absent: they are an implementation detail of the schema, not //! part of the domain. /// A Unix timestamp in **milliseconds** UTC, matching the `BIGINT` columns in /// the schema. pub type Timestamp = i64; /// The kind of a registered public key: an SSH key (used for both push auth and /// signature verification) or a GPG key (signature verification only). Stored as /// the `keys.kind` text `'ssh'` / `'gpg'`. #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum KeyKind { /// An `OpenSSH` public key. Ssh, /// An `OpenPGP` (GPG) public key. Gpg, } impl KeyKind { /// The lowercase database token (`"ssh"` / `"gpg"`). #[must_use] pub fn as_str(self) -> &'static str { match self { Self::Ssh => "ssh", Self::Gpg => "gpg", } } /// Parse from the database token, case-insensitively. #[must_use] pub fn from_token(s: &str) -> Option { match s.to_ascii_lowercase().as_str() { "ssh" => Some(Self::Ssh), "gpg" => Some(Self::Gpg), _ => None, } } } impl std::fmt::Display for KeyKind { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.write_str(self.as_str()) } } /// A repository's visibility, GitLab-style. Stored as the `repos.visibility` /// text token. #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] pub enum Visibility { /// Visible to everyone, anonymous included (subject to `allow_anonymous`). Public, /// Visible to any signed-in user, but not to anonymous visitors. Internal, /// Visible only to the owner, collaborators, and site admins. #[default] Private, } impl Visibility { /// The lowercase database/wire token. #[must_use] pub fn as_str(self) -> &'static str { match self { Self::Public => "public", Self::Internal => "internal", Self::Private => "private", } } /// Parse from the token, case-insensitively. #[must_use] pub fn from_token(s: &str) -> Option { match s.to_ascii_lowercase().as_str() { "public" => Some(Self::Public), "internal" => Some(Self::Internal), "private" => Some(Self::Private), _ => None, } } /// Whether the repository is private (only owner/collaborators/admins). #[must_use] pub fn is_private(self) -> bool { matches!(self, Self::Private) } /// A rank where higher is more visible (`private` < `internal` < `public`). /// Used to cap a fork's visibility at its parent's. #[must_use] pub fn rank(self) -> u8 { match self { Self::Private => 0, Self::Internal => 1, Self::Public => 2, } } } impl std::fmt::Display for Visibility { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.write_str(self.as_str()) } } /// One of a user's email addresses. Exactly one per user is primary; the primary /// is mirrored into [`User::email`]. #[derive(Debug, Clone, PartialEq, Eq)] pub struct UserEmail { /// ULID primary key. pub id: String, /// Owning user. pub user_id: String, /// The address as entered. pub email: String, /// Whether this is the user's primary address. pub is_primary: bool, /// When the address was verified, or `None` if still unverified. pub verified_at: Option, /// Creation time. pub created_at: Timestamp, } impl UserEmail { /// Whether the address has been verified. #[must_use] pub fn verified(&self) -> bool { self.verified_at.is_some() } } /// A registered account. #[derive(Debug, Clone, PartialEq, Eq)] pub struct User { /// ULID primary key. pub id: String, /// The name as the user typed it (case preserved for display). pub username: String, /// The primary email address (denormalized from [`UserEmail`] for quick /// access and case-insensitive login/uniqueness). pub email: String, /// Whether the primary email is shown on the public profile. pub email_public: bool, /// Optional human-friendly display name. pub display_name: Option, /// Optional pronouns, shown on the profile (e.g. `they/them`). pub pronouns: Option, /// Optional free-text bio shown on the profile. pub bio: Option, /// Optional location string. pub location: Option, /// Up to five arbitrary profile links (their brand icons are inferred from /// the URL host). pub links: Vec, /// Content type of the uploaded avatar, or `None` to use a generated /// identicon. The bytes live on disk, not in this row. pub avatar_mime: Option, /// Argon2id PHC hash, or `None` until an invite is completed. pub password_hash: Option, /// Whether the user must set a new password on next login. pub must_change_password: bool, /// Whether the user is a site administrator. pub is_admin: bool, /// When the account was disabled, if it is. pub disabled_at: Option, /// Creation time. pub created_at: Timestamp, /// Last modification time. pub updated_at: Timestamp, } /// A pending activation or password-reset invite. The emailed token is never /// stored; `token_hash` is its SHA-256, matched on redemption. #[derive(Debug, Clone, PartialEq, Eq)] pub struct Invite { /// ULID primary key. pub id: String, /// The user this invite activates. pub user_id: String, /// SHA-256 (hex) of the emailed token. pub token_hash: String, /// `"activate"` or `"reset"`. pub purpose: String, /// When the invite expires. pub expires_at: Timestamp, /// When it was redeemed, if it has been (single-use). pub used_at: Option, /// Creation time. pub created_at: Timestamp, } /// A nested, user-owned group. Groups provide the repository hierarchy; `path` is /// the materialized `group/sub/leaf` under the owner. #[derive(Debug, Clone, PartialEq, Eq)] pub struct Group { /// ULID primary key. pub id: String, /// Owning user. pub owner_id: String, /// Parent group, or `None` for a top-level group. pub parent_id: Option, /// The group's own name (the final path segment). pub name: String, /// Materialized full path within the owner, e.g. `group/sub/leaf`. pub path: String, /// Optional description. pub description: Option, /// Whether the group was created explicitly (persists) rather than /// auto-created to hold a repo (garbage-collected when empty). pub manual: bool, /// The group's visibility (gates its listing page). pub visibility: Visibility, /// Creation time. pub created_at: Timestamp, } /// A registered public key, for SSH push authentication and/or signature /// verification. #[derive(Debug, Clone, PartialEq, Eq)] pub struct Key { /// ULID primary key. pub id: String, /// Owning user. pub user_id: String, /// SSH or GPG. pub kind: KeyKind, /// Optional label. pub name: Option, /// `SHA256:…` for SSH, full hex for GPG. pub fingerprint: String, /// The `authorized_keys` line (SSH) or ASCII-armored block (GPG). pub public_key: String, /// Stable 1-based index within `(user, kind)`, used by `key del`. Deleting one /// key never renumbers the others. pub ordinal: i64, /// When the key was last used to authenticate, if ever. pub last_used_at: Option, /// When ownership of the key was verified, or `None` if unverified. SSH keys /// verify on first successful SSH auth; GPG keys via a signed challenge. pub verified_at: Option, /// Creation time. pub created_at: Timestamp, } impl Key { /// Whether ownership of the key has been verified. #[must_use] pub fn verified(&self) -> bool { self.verified_at.is_some() } } /// An API token record. The JWT itself is never stored; this row exists so a token /// can be listed and revoked (`revoked_at`), which the API checks on every request. #[derive(Debug, Clone, PartialEq, Eq)] pub struct ApiToken { /// ULID primary key, and the JWT `jti`. pub id: String, /// Owning user. pub user_id: String, /// Human label. pub name: String, /// Comma-separated scope list. pub scopes: String, /// Expiry, or `None` for a non-expiring token. pub expires_at: Option, /// When the token was revoked, if it has been. pub revoked_at: Option, /// When the token was last used, if ever. pub last_used_at: Option, /// Creation time. pub created_at: Timestamp, } /// A per-repo issue/PR label, optionally **scoped** (`kind: value`, e.g. /// `type: backend`). Scoped labels are mutually exclusive within their scope. #[derive(Debug, Clone, PartialEq, Eq)] pub struct Label { /// ULID primary key. pub id: String, /// Owning repository. pub repo_id: String, /// The label name, e.g. `bug` or `type: backend`. pub name: String, /// Hex colour (`#rrggbb`). pub color: String, /// Optional description. pub description: Option, /// Creation time. pub created_at: Timestamp, } impl Label { /// The label's scope (`kind`) when it is `kind: value`, else `None`. #[must_use] pub fn scope(&self) -> Option<&str> { self.name.split_once(": ").map(|(scope, _)| scope.trim()) } } /// Whether an issue/PR is open or closed. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum IssueState { /// Open. Open, /// Closed. Closed, } impl IssueState { /// The database token (`open` / `closed`). #[must_use] pub fn as_str(self) -> &'static str { match self { Self::Open => "open", Self::Closed => "closed", } } /// Parse from the database token. #[must_use] pub fn from_token(s: &str) -> Self { if s == "closed" { Self::Closed } else { Self::Open } } } /// An issue or (when `is_pull`) a pull request. Both share the `issues` table and /// the per-repo number counter, so `#N` is unique across both. #[derive(Debug, Clone, PartialEq, Eq)] pub struct Issue { /// ULID primary key. pub id: String, /// Owning repository. pub repo_id: String, /// The per-repo number shown as `#N`. pub number: Timestamp, /// The author. pub author_id: String, /// Title. pub title: String, /// Markdown body. pub body: String, /// Open or closed. pub state: IssueState, /// Whether this row is a pull request. pub is_pull: bool, /// The assignee, if any. pub assignee_id: Option, /// Creation time. pub created_at: Timestamp, /// Last update time. pub updated_at: Timestamp, /// When it was closed, if it is. pub closed_at: Option, /// When the conversation was locked, or `None` if unlocked. pub locked_at: Option, } impl Issue { /// Whether the conversation is locked (only writers may comment). #[must_use] pub fn locked(&self) -> bool { self.locked_at.is_some() } } /// A comment on an issue or pull request. #[derive(Debug, Clone, PartialEq, Eq)] pub struct Comment { /// ULID primary key. pub id: String, /// The issue/PR this comment belongs to. pub issue_id: String, /// The author. pub author_id: String, /// Markdown body. pub body: String, /// Creation time. pub created_at: Timestamp, /// Last edit time. pub updated_at: Timestamp, } /// The pull-request side table for an `issues` row with `is_pull = 1`. #[derive(Debug, Clone, PartialEq, Eq)] pub struct PullRequest { /// The `issues` row id. pub issue_id: String, /// The base ref (merged into). pub base_ref: String, /// The head ref (source of the changes). pub head_ref: String, /// When it was merged, if it has been. pub merged_at: Option, /// Who merged it. pub merged_by: Option, /// The resulting merge commit sha. pub merge_sha: Option, /// The strategy used (`merge` | `squash` | `rebase`). pub merge_strategy: Option, } /// A git repository. On disk it lives at `{repo_dir}/{id[0..2]}/{id}.git`; the /// database is the sole authority mapping `(owner, path)` to this id. // Several independent feature toggles (issues/pulls/releases/mirror) are flags, // not a state machine — a struct of bools is the clearest representation. #[allow(clippy::struct_excessive_bools)] #[derive(Debug, Clone, PartialEq, Eq)] pub struct Repo { /// ULID primary key, and the on-disk directory stem. pub id: String, /// Owning user. pub owner_id: String, /// Containing group, or `None` when the repo hangs directly off the owner. pub group_id: Option, /// The repo's own name (the final path segment). pub name: String, /// Materialized full path within the owner, e.g. `group/sub/repo`; equal to /// `name` when ungrouped. pub path: String, /// Optional one-line description. pub description: Option, /// Who may see the repository (public / internal / private). pub visibility: Visibility, /// The branch shown by default and used for HEAD. pub default_branch: String, /// The object hash algorithm (`sha1` or `sha256`), fixed at creation. pub object_format: String, /// Whether issues are enabled for this repository. pub issues_enabled: bool, /// Whether pull requests are enabled for this repository. pub pulls_enabled: bool, /// Whether releases are enabled for this repository. pub releases_enabled: bool, /// Whether this repository is a mirror (kept in sync from a remote). pub is_mirror: bool, /// The repository this one was forked from, if any. pub fork_parent_id: Option, /// The next issue/PR number to allocate (shared counter). pub next_iid: Timestamp, /// When the repo was archived (read-only), if it is. pub archived_at: Option, /// Approximate on-disk size in bytes, refreshed by maintenance. pub size_bytes: i64, /// Time of the last push, if any. pub pushed_at: Option, /// Creation time. pub created_at: Timestamp, /// Last modification time. pub updated_at: Timestamp, }