steid

@jamesgill /

steid/src/domain/repository/issue_repo.rs
Rust3.4 KBCode·Blame·History·Raw
1use super::RepositoryResult;
2use crate::domain::{Issue, IssueComment, IssueId, IssueNumber, IssueState, RepoId};
3
4/// How many issues a repository has in each state.
5///
6/// Both counts in one round trip because the list toolbar always shows both — asking
7/// twice would be two queries to render one line.
8#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
9pub struct IssueCounts {
10 pub open: u64,
11 pub closed: u64,
12}
13
14/// Persistence for the [`Issue`] aggregate.
15///
16/// Comments live behind the same port rather than one of their own. A comment is not an
17/// aggregate root: it has no meaning without its issue, nothing addresses one except
18/// through the issue it hangs off, and splitting them would mean a use case holding two
19/// ports that can only ever be used together.
20pub trait IssueRepository: Send + Sync {
21 /// Looks an issue up by the `/{handle}/repos/{name}/issues/{number}` pair.
22 fn find_by_number(
23 &self,
24 repo_id: &RepoId,
25 number: IssueNumber,
26 ) -> impl Future<Output = RepositoryResult<Option<Issue>>> + Send;
27
28 /// One page of a repository's issues in the given state, newest first — which for a
29 /// monotonic sequence is descending number, an order that cannot tie.
30 ///
31 /// `offset` and `limit` are the list page's `?page=`. A `limit` past the end of the
32 /// list is a short page, not an error: an issue closed between two page loads is
33 /// ordinary, and so is a stale link.
34 ///
35 /// **The label filter does not go through these two.** Which labels an issue wears
36 /// is a second table the use case already reads whole to render the pills, so a
37 /// filtered list is narrowed and paged there — see `list_issues`. That is why the
38 /// filtered path asks for every issue and this one exists for the unfiltered path.
39 fn list_by_repo(
40 &self,
41 repo_id: &RepoId,
42 state: IssueState,
43 offset: usize,
44 limit: usize,
45 ) -> impl Future<Output = RepositoryResult<Vec<Issue>>> + Send;
46
47 /// How many issues the repository has open and closed.
48 fn counts(
49 &self,
50 repo_id: &RepoId,
51 ) -> impl Future<Output = RepositoryResult<IssueCounts>> + Send;
52
53 /// How many comments each issue on the repository carries.
54 ///
55 /// Per repository rather than per issue because the list renders a count on every
56 /// row: asking per row is a query per row.
57 fn comment_counts(
58 &self,
59 repo_id: &RepoId,
60 ) -> impl Future<Output = RepositoryResult<Vec<(IssueId, u64)>>> + Send;
61
62 /// Inserts or replaces an issue.
63 ///
64 /// The owning repository must already exist; the foreign key runs that direction.
65 fn save(&self, issue: &Issue) -> impl Future<Output = RepositoryResult<()>> + Send;
66
67 /// Every comment on an issue, oldest first — a conversation reads forwards.
68 ///
69 /// Comments written in the same second keep the order they were written in, which
70 /// both implementations get from insertion order rather than from the id: ids are
71 /// random UUIDs, so a tie broken by one would occasionally read backwards.
72 fn list_comments(
73 &self,
74 issue_id: &IssueId,
75 ) -> impl Future<Output = RepositoryResult<Vec<IssueComment>>> + Send;
76
77 /// Adds a comment to an issue.
78 fn add_comment(
79 &self,
80 comment: &IssueComment,
81 ) -> impl Future<Output = RepositoryResult<()>> + Send;
82}