| 1 | //! Persisted domain types, defined as Toasty models. Tables are created from |
| 2 | //! these definitions via [`crate::db`]'s `push_schema`. |
| 3 | //! |
| 4 | //! Foreign keys are kept as plain scalar fields (`owner_id`, `user_id`) and |
| 5 | //! queried explicitly, rather than declaring Toasty relations — simpler and a |
| 6 | //! good fit for our small schema. |
| 7 | |
| 8 | /// A registered user account. |
| 9 | #[derive(Debug, toasty::Model)] |
| 10 | pub struct User { |
| 11 | #[key] |
| 12 | #[auto] |
| 13 | pub id: i64, |
| 14 | #[unique] |
| 15 | pub username: String, |
| 16 | pub email: String, |
| 17 | /// Argon2 PHC-format password hash. |
| 18 | pub password_hash: String, |
| 19 | pub is_admin: bool, |
| 20 | /// Unix timestamp (seconds) of account creation. |
| 21 | pub created_at: i64, |
| 22 | } |
| 23 | |
| 24 | /// A hosted repository, owned by a [`User`]. |
| 25 | #[derive(Debug, toasty::Model)] |
| 26 | pub struct Repository { |
| 27 | #[key] |
| 28 | #[auto] |
| 29 | pub id: i64, |
| 30 | #[index] |
| 31 | pub owner_id: i64, |
| 32 | pub name: String, |
| 33 | pub description: String, |
| 34 | pub is_private: bool, |
| 35 | /// Short name of the default branch, e.g. `main`. |
| 36 | pub default_branch: String, |
| 37 | pub created_at: i64, |
| 38 | /// Push-mirror remote: after every successful push, refs are mirrored to |
| 39 | /// this git URL (`git push --mirror`). Empty disables mirroring. New |
| 40 | /// columns go last so `ALTER TABLE ADD COLUMN` on existing databases |
| 41 | /// agrees with the fresh-schema column order. |
| 42 | pub mirror_url: String, |
| 43 | } |
| 44 | |
| 45 | /// A CI run for a pushed commit. |
| 46 | /// |
| 47 | /// `status` is one of `queued`, `running`, `success`, `failure` (a step exited |
| 48 | /// non-zero), or `error` (the runner itself failed). `started_at`/`finished_at` |
| 49 | /// are 0 until they occur. |
| 50 | #[derive(Clone, Debug, toasty::Model)] |
| 51 | pub struct CiRun { |
| 52 | #[key] |
| 53 | #[auto] |
| 54 | pub id: i64, |
| 55 | #[index] |
| 56 | pub repo_id: i64, |
| 57 | /// Full commit SHA the run is for. |
| 58 | pub commit: String, |
| 59 | /// Short branch name that was pushed (e.g. `main`). |
| 60 | pub ref_name: String, |
| 61 | pub status: String, |
| 62 | /// Accumulated run log. |
| 63 | pub log: String, |
| 64 | pub created_at: i64, |
| 65 | pub started_at: i64, |
| 66 | pub finished_at: i64, |
| 67 | } |
| 68 | |
| 69 | /// One artifact produced by a CI run, stored on disk under |
| 70 | /// `data_dir/artifacts/{repo_id}/{commit}/` (see `docs/ci-artifacts.md`). |
| 71 | /// |
| 72 | /// `commit` is denormalized from the run so per-commit lookups (the |
| 73 | /// latest-on-branch alias) don't join through runs. |
| 74 | #[derive(Clone, Debug, toasty::Model)] |
| 75 | pub struct CiArtifact { |
| 76 | #[key] |
| 77 | #[auto] |
| 78 | pub id: i64, |
| 79 | #[index] |
| 80 | pub run_id: i64, |
| 81 | #[index] |
| 82 | pub repo_id: i64, |
| 83 | /// Full commit SHA the producing run was for. |
| 84 | pub commit: String, |
| 85 | /// Declared artifact name (unique within a pipeline, not globally). |
| 86 | pub name: String, |
| 87 | /// Total size in bytes (summed over files for directory artifacts). |
| 88 | pub size: i64, |
| 89 | /// Directory artifact (stored as a tarball, or extracted when `browse`). |
| 90 | pub is_dir: bool, |
| 91 | /// Served as a browsable static site rather than a download. |
| 92 | pub browse: bool, |
| 93 | /// JSON object of metadata-extractor key → output. |
| 94 | pub meta: String, |
| 95 | pub created_at: i64, |
| 96 | } |
| 97 | |
| 98 | /// An issue on a repository. `number` is the user-facing per-repo sequence |
| 99 | /// (`#1`, `#2`, …); `id` stays the global key. `state` is `open` or `closed`. |
| 100 | /// |
| 101 | /// Numbering is assigned as max+1 at creation; with a single server process |
| 102 | /// (our deployment shape) that cannot race. |
| 103 | #[derive(Clone, Debug, toasty::Model)] |
| 104 | pub struct Issue { |
| 105 | #[key] |
| 106 | #[auto] |
| 107 | pub id: i64, |
| 108 | #[index] |
| 109 | pub repo_id: i64, |
| 110 | pub number: i64, |
| 111 | pub title: String, |
| 112 | /// Markdown body (may be empty). |
| 113 | pub body: String, |
| 114 | pub author_id: i64, |
| 115 | pub state: String, |
| 116 | pub created_at: i64, |
| 117 | /// Bumped on comments and state changes, for "recently active" ordering. |
| 118 | pub updated_at: i64, |
| 119 | } |
| 120 | |
| 121 | /// A comment on an [`Issue`]. |
| 122 | #[derive(Clone, Debug, toasty::Model)] |
| 123 | pub struct IssueComment { |
| 124 | #[key] |
| 125 | #[auto] |
| 126 | pub id: i64, |
| 127 | #[index] |
| 128 | pub issue_id: i64, |
| 129 | pub author_id: i64, |
| 130 | /// Markdown body. |
| 131 | pub body: String, |
| 132 | pub created_at: i64, |
| 133 | } |
| 134 | |
| 135 | /// A web login session, keyed by an opaque random token stored in a cookie. |
| 136 | #[derive(Debug, toasty::Model)] |
| 137 | pub struct Session { |
| 138 | #[key] |
| 139 | pub token: String, |
| 140 | #[index] |
| 141 | pub user_id: i64, |
| 142 | pub created_at: i64, |
| 143 | /// Unix timestamp (seconds) after which the session is invalid. |
| 144 | pub expires_at: i64, |
| 145 | } |
| 146 | |
| 147 | /// An uploaded file (e.g. an image pasted into the file editor), stored |
| 148 | /// outside git at `data_dir/attachments/{repo_id}/{hash}` so large binaries |
| 149 | /// never enter the repository's history. Markdown carries only the serve URL. |
| 150 | /// |
| 151 | /// Content-addressed: `hash` is the lowercase hex SHA-256 of the bytes, so the |
| 152 | /// same content uploaded twice to a repo dedupes to one file. Lookups and GC |
| 153 | /// scope by `repo_id`, which also gates serving by the repo's read access. |
| 154 | #[derive(Clone, Debug, toasty::Model)] |
| 155 | pub struct Attachment { |
| 156 | #[key] |
| 157 | #[auto] |
| 158 | pub id: i64, |
| 159 | #[index] |
| 160 | pub repo_id: i64, |
| 161 | /// Lowercase hex SHA-256 of the content — both the dedup key and the path |
| 162 | /// component under the repo's attachment directory. |
| 163 | pub hash: String, |
| 164 | /// MIME type to serve the bytes with (e.g. `image/png`). |
| 165 | pub content_type: String, |
| 166 | pub size: i64, |
| 167 | /// The user who first uploaded this content to the repo. |
| 168 | pub uploader_id: i64, |
| 169 | pub created_at: i64, |
| 170 | } |
| 171 | |
| 172 | /// A personal access token: a long-lived, scoped bearer credential for |
| 173 | /// non-browser API clients (e.g. tooling that fetches attachments). Only the |
| 174 | /// SHA-256 hash of the token is stored; the plaintext is shown once at |
| 175 | /// creation. A PAT is least-privilege by design — its `scopes` bound what it |
| 176 | /// can do, and the only scope today (`read`) authenticates safe (GET/HEAD) |
| 177 | /// requests only, so a leaked token can never mutate. |
| 178 | #[derive(Clone, Debug, toasty::Model)] |
| 179 | pub struct ApiToken { |
| 180 | #[key] |
| 181 | #[auto] |
| 182 | pub id: i64, |
| 183 | #[index] |
| 184 | pub user_id: i64, |
| 185 | /// A human label for the token (e.g. "claude"), for listing/revoking. |
| 186 | pub name: String, |
| 187 | /// Lowercase hex SHA-256 of the token; the lookup key. |
| 188 | #[unique] |
| 189 | pub token_hash: String, |
| 190 | /// Comma-separated scopes granted to this token (e.g. `read`). |
| 191 | pub scopes: String, |
| 192 | pub created_at: i64, |
| 193 | } |
| 194 | |
| 195 | /// A registered SSH public key, used to authenticate git-over-SSH connections. |
| 196 | #[derive(Debug, toasty::Model)] |
| 197 | pub struct SshKey { |
| 198 | #[key] |
| 199 | #[auto] |
| 200 | pub id: i64, |
| 201 | #[index] |
| 202 | pub user_id: i64, |
| 203 | pub title: String, |
| 204 | /// Canonical SHA256 fingerprint, e.g. `SHA256:…`. |
| 205 | #[unique] |
| 206 | pub fingerprint: String, |
| 207 | /// Normalized OpenSSH public-key line. |
| 208 | pub content: String, |
| 209 | pub created_at: i64, |
| 210 | } |