2.8 KBRaw
| 1 | use std::time::SystemTime; |
| 2 | |
| 3 | use super::RepositoryResult; |
| 4 | use crate::domain::{OrgId, RepoId, RepoName, Repository}; |
| 5 | |
| 6 | /// Persistence for [`Repository`]. |
| 7 | pub trait RepoRepository: Send + Sync { |
| 8 | fn find_by_id( |
| 9 | &self, |
| 10 | id: &RepoId, |
| 11 | ) -> impl Future<Output = RepositoryResult<Option<Repository>>> + Send; |
| 12 | |
| 13 | /// Looks a repository up by its owner and name — the `/{handle}/repos/{name}` pair. |
| 14 | fn find_by_org_and_name( |
| 15 | &self, |
| 16 | org_id: &OrgId, |
| 17 | name: &RepoName, |
| 18 | ) -> impl Future<Output = RepositoryResult<Option<Repository>>> + Send; |
| 19 | |
| 20 | /// Every repository owned by an organisation, most recently updated first, ties |
| 21 | /// broken by name. |
| 22 | /// |
| 23 | /// Recency rather than alphabetical because the profile is a portfolio: alphabetical |
| 24 | /// is a filing rule and puts `dotfiles` above the thing being built. The name |
| 25 | /// tiebreak is what keeps a listing stable when several repositories share a second |
| 26 | /// — every repository that existed before `updated_at` did shares one exactly. |
| 27 | /// |
| 28 | /// Returns them all regardless of visibility. Filtering is an authorization |
| 29 | /// decision and belongs to the use case, so that the page and `/api` cannot end up |
| 30 | /// applying different rules. |
| 31 | fn list_by_org( |
| 32 | &self, |
| 33 | org_id: &OrgId, |
| 34 | ) -> impl Future<Output = RepositoryResult<Vec<Repository>>> + Send; |
| 35 | |
| 36 | /// Inserts or replaces a repository. |
| 37 | /// |
| 38 | /// The owning organisation must already exist; the foreign key runs that direction. |
| 39 | fn save(&self, repo: &Repository) -> impl Future<Output = RepositoryResult<()>> + Send; |
| 40 | |
| 41 | /// Moves a repository's `updated_at` to `now`, doing nothing if it is gone. |
| 42 | /// |
| 43 | /// The time is supplied rather than read here, so a test can say when a push |
| 44 | /// happened instead of racing the clock — and so the two implementations cannot |
| 45 | /// disagree about which clock they read. |
| 46 | /// |
| 47 | /// Succeeding on a missing row keeps the caller out of the business of a repository |
| 48 | /// deleted mid-push; there is nothing useful it could do about it either way. |
| 49 | fn touch( |
| 50 | &self, |
| 51 | id: &RepoId, |
| 52 | now: SystemTime, |
| 53 | ) -> impl Future<Output = RepositoryResult<()>> + Send; |
| 54 | |
| 55 | /// Deletes a repository, succeeding if there was nothing to delete. |
| 56 | /// |
| 57 | /// A delete rather than a flag, for the same reason token revocation is: a row that |
| 58 | /// lingers is a repository that stops appearing only as long as every read remembers |
| 59 | /// to check. The bare repo on disk is a separate write and not this port's business. |
| 60 | /// |
| 61 | /// Succeeding on a missing row keeps the caller idempotent — two clicks on a delete |
| 62 | /// button must not turn the second one into an error. |
| 63 | fn delete(&self, id: &RepoId) -> impl Future<Output = RepositoryResult<()>> + Send; |
| 64 | } |