Skip to main content

max / goingson

3.7 KB · 94 lines History Blame Raw
1 use super::{
2 DateTime, NewProblem, Problem, ProblemId, ProblemStatus, ProjectId, Result, TaskId, UserId,
3 Utc, async_trait,
4 };
5
6 /// Filters for listing problems. All fields are ANDed; `None` means unfiltered.
7 #[derive(Debug, Clone, Default)]
8 pub struct ProblemFilter {
9 /// Restrict to one adapter, e.g. `wam`.
10 pub source: Option<String>,
11 /// Restrict to one triage state. The inbox passes `Open`.
12 pub status: Option<ProblemStatus>,
13 /// Restrict to problems attributed to one project.
14 pub project_id: Option<ProjectId>,
15 }
16
17 /// Repository for the Problems inbox.
18 ///
19 /// Problems are a mirror of external sources, so the write surface splits in
20 /// two: [`ProblemRepository::ingest`] is for adapters and upserts on
21 /// `(source, source_ref)`, while the triage methods are for the human and are
22 /// never touched by a re-pull.
23 ///
24 /// All operations are scoped to a specific user for multi-tenancy support.
25 #[async_trait]
26 pub trait ProblemRepository: Send + Sync {
27 /// Lists problems matching a filter.
28 ///
29 /// Ordered by painhours descending, ties broken by newest. That score moves
30 /// with the clock, so it is computed and sorted after the fetch rather than
31 /// in SQL.
32 async fn list(&self, user_id: UserId, filter: &ProblemFilter) -> Result<Vec<Problem>>;
33
34 /// Retrieves a problem by ID, returning `None` if not found.
35 async fn get_by_id(&self, id: ProblemId, user_id: UserId) -> Result<Option<Problem>>;
36
37 /// Retrieves a problem by its provenance key.
38 async fn find_by_source_ref(
39 &self,
40 user_id: UserId,
41 source: &str,
42 source_ref: &str,
43 ) -> Result<Option<Problem>>;
44
45 /// Ingests a problem from a source, inserting it or updating the existing
46 /// row with the same `(source, source_ref)`.
47 ///
48 /// Upserts refresh what the source owns (title, body, pain, scale,
49 /// timestamps, `last_seen_at`) and leave what GoingsOn owns alone, so
50 /// re-pulling never undoes a triage decision. The one exception is a problem
51 /// the source reports as fixed, which settles an untriaged row as
52 /// `Resolved`; a problem already promoted or dismissed keeps that state.
53 async fn ingest(&self, user_id: UserId, problem: NewProblem) -> Result<Problem>;
54
55 /// Records that a task was created from this problem: sets `Promoted`, the
56 /// backlink, and the settle instant that freezes its score.
57 ///
58 /// Returns `None` if the problem does not exist.
59 async fn promote(
60 &self,
61 id: ProblemId,
62 user_id: UserId,
63 task_id: TaskId,
64 ) -> Result<Option<Problem>>;
65
66 /// Sets triage state directly, for dismissing and for reopening something
67 /// settled by mistake. Clears the backlink and the settle instant when
68 /// moving back to `Open`.
69 async fn set_status(
70 &self,
71 id: ProblemId,
72 user_id: UserId,
73 status: ProblemStatus,
74 ) -> Result<Option<Problem>>;
75
76 /// Attributes a problem to a project, or clears the attribution.
77 async fn set_project(
78 &self,
79 id: ProblemId,
80 user_id: UserId,
81 project_id: Option<ProjectId>,
82 ) -> Result<Option<Problem>>;
83
84 /// Deletes a problem. Triage history is worth keeping, so prefer dismissing;
85 /// this exists for genuinely bad ingests.
86 async fn delete(&self, id: ProblemId, user_id: UserId) -> Result<bool>;
87
88 /// The most recent `last_seen_at` across a source's problems, i.e. when that
89 /// adapter last completed a pull. Callers compare a problem's `last_seen_at`
90 /// against this to decide staleness; `None` means the source has never been
91 /// pulled.
92 async fn last_pulled_at(&self, user_id: UserId, source: &str) -> Result<Option<DateTime<Utc>>>;
93 }
94