anvilsign in

collin/anvil

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)]
10pub 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)]
26pub 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 /// SHA-256 hash of the preview image extracted from README (empty if none).
44 pub preview_image_hash: String,
45 /// Primary language detected in the repository (e.g. "Rust", empty if no files).
46 pub primary_language: String,
47 /// JSON array of language percentages: [{"lang": "Rust", "percent": 75.5}, ...].
48 pub languages_json: String,
49}
50
51/// A CI run for a pushed commit.
52///
53/// `status` is one of `queued`, `running`, `success`, `failure` (a step exited
54/// non-zero), or `error` (the runner itself failed). `started_at`/`finished_at`
55/// are 0 until they occur.
56#[derive(Clone, Debug, toasty::Model)]
57pub struct CiRun {
58 #[key]
59 #[auto]
60 pub id: i64,
61 #[index]
62 pub repo_id: i64,
63 /// Full commit SHA the run is for.
64 pub commit: String,
65 /// Short branch name that was pushed (e.g. `main`).
66 pub ref_name: String,
67 pub status: String,
68 /// Accumulated run log.
69 pub log: String,
70 pub created_at: i64,
71 pub started_at: i64,
72 pub finished_at: i64,
73}
74
75/// One artifact produced by a CI run, stored on disk under
76/// `data_dir/artifacts/{repo_id}/{commit}/` (see `docs/ci-artifacts.md`).
77///
78/// `commit` is denormalized from the run so per-commit lookups (the
79/// latest-on-branch alias) don't join through runs.
80#[derive(Clone, Debug, toasty::Model)]
81pub struct CiArtifact {
82 #[key]
83 #[auto]
84 pub id: i64,
85 #[index]
86 pub run_id: i64,
87 #[index]
88 pub repo_id: i64,
89 /// Full commit SHA the producing run was for.
90 pub commit: String,
91 /// Declared artifact name (unique within a pipeline, not globally).
92 pub name: String,
93 /// Total size in bytes (summed over files for directory artifacts).
94 pub size: i64,
95 /// Directory artifact (stored as a tarball, or extracted when `browse`).
96 pub is_dir: bool,
97 /// Served as a browsable static site rather than a download.
98 pub browse: bool,
99 /// JSON object of metadata-extractor key → output.
100 pub meta: String,
101 pub created_at: i64,
102}
103
104/// An issue on a repository. `number` is the user-facing per-repo sequence
105/// (`#1`, `#2`, …); `id` stays the global key. `state` is `open` or `closed`.
106///
107/// Numbering is assigned as max+1 at creation; with a single server process
108/// (our deployment shape) that cannot race.
109#[derive(Clone, Debug, toasty::Model)]
110pub struct Issue {
111 #[key]
112 #[auto]
113 pub id: i64,
114 #[index]
115 pub repo_id: i64,
116 pub number: i64,
117 pub title: String,
118 /// Markdown body (may be empty).
119 pub body: String,
120 pub author_id: i64,
121 pub state: String,
122 pub created_at: i64,
123 /// Bumped on comments and state changes, for "recently active" ordering.
124 pub updated_at: i64,
125}
126
127/// A comment on an [`Issue`].
128#[derive(Clone, Debug, toasty::Model)]
129pub struct IssueComment {
130 #[key]
131 #[auto]
132 pub id: i64,
133 #[index]
134 pub issue_id: i64,
135 pub author_id: i64,
136 /// Markdown body.
137 pub body: String,
138 pub created_at: i64,
139}
140
141/// A web login session, keyed by an opaque random token stored in a cookie.
142#[derive(Debug, toasty::Model)]
143pub struct Session {
144 #[key]
145 pub token: String,
146 #[index]
147 pub user_id: i64,
148 pub created_at: i64,
149 /// Unix timestamp (seconds) after which the session is invalid.
150 pub expires_at: i64,
151}
152
153/// An uploaded file (e.g. an image pasted into the file editor), stored
154/// outside git at `data_dir/attachments/{repo_id}/{hash}` so large binaries
155/// never enter the repository's history. Markdown carries only the serve URL.
156///
157/// Content-addressed: `hash` is the lowercase hex SHA-256 of the bytes, so the
158/// same content uploaded twice to a repo dedupes to one file. Lookups and GC
159/// scope by `repo_id`, which also gates serving by the repo's read access.
160#[derive(Clone, Debug, toasty::Model)]
161pub struct Attachment {
162 #[key]
163 #[auto]
164 pub id: i64,
165 #[index]
166 pub repo_id: i64,
167 /// Lowercase hex SHA-256 of the content — both the dedup key and the path
168 /// component under the repo's attachment directory.
169 pub hash: String,
170 /// MIME type to serve the bytes with (e.g. `image/png`).
171 pub content_type: String,
172 pub size: i64,
173 /// The user who first uploaded this content to the repo.
174 pub uploader_id: i64,
175 pub created_at: i64,
176}
177
178/// A personal access token: a long-lived, scoped bearer credential for
179/// non-browser API clients (e.g. tooling that fetches attachments). Only the
180/// SHA-256 hash of the token is stored; the plaintext is shown once at
181/// creation. A PAT is least-privilege by design — its `scopes` bound what it
182/// can do, and the only scope today (`read`) authenticates safe (GET/HEAD)
183/// requests only, so a leaked token can never mutate.
184#[derive(Clone, Debug, toasty::Model)]
185pub struct ApiToken {
186 #[key]
187 #[auto]
188 pub id: i64,
189 #[index]
190 pub user_id: i64,
191 /// A human label for the token (e.g. "claude"), for listing/revoking.
192 pub name: String,
193 /// Lowercase hex SHA-256 of the token; the lookup key.
194 #[unique]
195 pub token_hash: String,
196 /// Comma-separated scopes granted to this token (e.g. `read`).
197 pub scopes: String,
198 pub created_at: i64,
199}
200
201/// A registered SSH public key, used to authenticate git-over-SSH connections.
202#[derive(Debug, toasty::Model)]
203pub struct SshKey {
204 #[key]
205 #[auto]
206 pub id: i64,
207 #[index]
208 pub user_id: i64,
209 pub title: String,
210 /// Canonical SHA256 fingerprint, e.g. `SHA256:…`.
211 #[unique]
212 pub fingerprint: String,
213 /// Normalized OpenSSH public-key line.
214 pub content: String,
215 pub created_at: i64,
216}
217
218/// A per-repository secret, stored only as a sealed envelope.
219///
220/// The server cannot read `envelope`: it is encrypted to the owner's
221/// ssh-ed25519 keys by the client that set it (see [`crate::secrets`]).
222/// `recipients` denormalizes the envelope's fingerprints so the UI can tell,
223/// without opening anything, which secrets a newly registered key still cannot
224/// decrypt — those need `anvild secret rekey`.
225#[derive(Clone, Debug, toasty::Model)]
226pub struct RepoSecret {
227 #[key]
228 #[auto]
229 pub id: i64,
230 #[index]
231 pub repo_id: i64,
232 /// Environment variable name, e.g. `DEPLOY_TOKEN`. Unique per repository.
233 pub name: String,
234 /// The sealed envelope, as JSON (`anvil-secret-v1`).
235 pub envelope: String,
236 /// Comma-separated SSH fingerprints the envelope is sealed to.
237 pub recipients: String,
238 pub created_at: i64,
239 pub updated_at: i64,
240}
241
242/// Cached admin metrics computed periodically (e.g., disk usage snapshot).
243#[derive(Clone, Debug, toasty::Model)]
244pub struct AdminCache {
245 #[key]
246 #[auto]
247 pub id: i64,
248 /// Cache key (e.g., "disk_usage").
249 pub key: String,
250 /// JSON-encoded cached data.
251 pub value: String,
252 /// Unix timestamp (seconds) of when this snapshot was taken.
253 pub computed_at: i64,
254}