OXYGENHUB
# c40c2d3edb51 oxygenhub / crates / hub / src / db / activity.rs
4.9 KiB · Rust · last changed Dashboard for signed-in users 1 month ago source blame history raw
1
//! Cross-repository reads for the signed-in dashboard (hub spec §6):
2
//! the activity feed and the proposals waiting on a reviewer. Callers
3
//! pass the repository ids the viewer may see, so nothing here decides
4
//! access.
5
6
use super::Pool;
7
use sqlx::FromRow;
8
9
/// One line of the activity feed.
10
#[derive(Clone, Debug, FromRow)]
11
pub struct Activity {
12
    pub owner: String,
13
    pub name: String,
14
    pub at_ms: i64,
15
    pub actor: String,
16
    /// `push`, `release`, `proposal` or `issue`.
17
    pub kind: String,
18
    /// What happened: the event name, or the bookmark for a push.
19
    pub event: String,
20
    /// The proposal or issue title, the release tag.
21
    pub title: String,
22
    /// The proposal or issue number, 0 otherwise.
23
    pub number: i64,
24
}
25
26
impl Activity {
27
    /// Where the line links to.
28
    pub fn url(&self) -> String {
29
        let base = format!("/{}/{}", self.owner, self.name);
30
        match self.kind.as_str() {
31
            "proposal" => format!("{base}/proposal/{}", self.number),
32
            "issue" => format!("{base}/issue/{}", self.number),
33
            "release" => format!("{base}/releases/tag/{}", self.title),
34
            _ => format!("{base}/pushes"),
35
        }
36
    }
37
}
38
39
fn ids_json(ids: &[i64]) -> String {
40
    serde_json::to_string(ids).unwrap_or_else(|_| "[]".into())
41
}
42
43
/// The newest `limit` events across the repositories in `repo_ids`.
44
pub async fn feed(pool: &Pool, repo_ids: &[i64], limit: i64) -> sqlx::Result<Vec<Activity>> {
45
    sqlx::query_as(
46
        "WITH seen(id) AS (SELECT value FROM json_each(?)), ev AS ( \
47
           SELECT repo_id, at_ms, pusher AS actor, 'push' AS kind, reference AS event, '' AS title, 0 AS number FROM pushes WHERE ok = 1 \
48
           UNION ALL SELECT repo_id, created_ms, author, 'release', 'published', tag, 0 FROM releases \
49
           UNION ALL SELECT p.repo_id, e.at_ms, e.actor, 'proposal', e.event, p.title, p.number FROM proposal_events e JOIN proposals p ON p.id = e.proposal_id \
50
           UNION ALL SELECT i.repo_id, e.at_ms, e.actor, 'issue', e.event, i.title, i.number FROM issue_events e JOIN issues i ON i.id = e.issue_id) \
51
         SELECT r.owner, r.name, ev.at_ms, ev.actor, ev.kind, ev.event, ev.title, ev.number \
52
         FROM ev JOIN repositories r ON r.id = ev.repo_id WHERE ev.repo_id IN (SELECT id FROM seen) \
53
         ORDER BY ev.at_ms DESC LIMIT ?",
54
    )
55
    .bind(ids_json(repo_ids))
56
    .bind(limit)
57
    .fetch_all(pool)
58
    .await
59
}
60
61
/// A proposal on the dashboard, with its repository's name.
62
#[derive(Clone, Debug, FromRow)]
63
pub struct ProposalLine {
64
    pub owner: String,
65
    pub name: String,
66
    pub number: i64,
67
    pub title: String,
68
    pub author: String,
69
    pub state: String,
70
    pub conflicted: bool,
71
    pub updated_ms: i64,
72
}
73
74
/// Open proposals in `repo_ids` by someone else that `account_id` has
75
/// not reviewed yet: the review queue.
76
pub async fn awaiting_review(pool: &Pool, account_id: i64, repo_ids: &[i64]) -> sqlx::Result<Vec<ProposalLine>> {
77
    sqlx::query_as(
78
        "SELECT r.owner, r.name, p.number, p.title, p.author, p.state, p.conflicted, p.updated_ms \
79
         FROM proposals p JOIN repositories r ON r.id = p.repo_id \
80
         WHERE p.repo_id IN (SELECT value FROM json_each(?)) AND p.state = 'open' \
81
           AND (p.author_id IS NULL OR p.author_id != ?) \
82
           AND NOT EXISTS (SELECT 1 FROM proposal_reviews v WHERE v.proposal_id = p.id AND v.author_id = ?) \
83
         ORDER BY p.updated_ms DESC",
84
    )
85
    .bind(ids_json(repo_ids))
86
    .bind(account_id)
87
    .bind(account_id)
88
    .fetch_all(pool)
89
    .await
90
}
91
92
/// `account_id`'s own open and draft proposals in repositories it can
93
/// still see.
94
pub async fn authored(pool: &Pool, account_id: i64, repo_ids: &[i64]) -> sqlx::Result<Vec<ProposalLine>> {
95
    sqlx::query_as(
96
        "SELECT r.owner, r.name, p.number, p.title, p.author, p.state, p.conflicted, p.updated_ms \
97
         FROM proposals p JOIN repositories r ON r.id = p.repo_id \
98
         WHERE p.author_id = ? AND p.state IN ('open', 'draft') AND p.repo_id IN (SELECT value FROM json_each(?)) \
99
         ORDER BY p.updated_ms DESC",
100
    )
101
    .bind(account_id)
102
    .bind(ids_json(repo_ids))
103
    .fetch_all(pool)
104
    .await
105
}
106
107
/// An issue on the dashboard, with its repository's name.
108
#[derive(Clone, Debug, FromRow)]
109
pub struct IssueLine {
110
    pub owner: String,
111
    pub name: String,
112
    pub number: i64,
113
    pub title: String,
114
    pub updated_ms: i64,
115
}
116
117
/// Open issues assigned to `account_id`, in repositories it can see.
118
pub async fn assigned(pool: &Pool, account_id: i64, repo_ids: &[i64]) -> sqlx::Result<Vec<IssueLine>> {
119
    sqlx::query_as(
120
        "SELECT r.owner, r.name, i.number, i.title, i.updated_ms \
121
         FROM issues i JOIN issue_assignees a ON a.issue_id = i.id JOIN repositories r ON r.id = i.repo_id \
122
         WHERE a.account_id = ? AND i.state = 'open' AND i.repo_id IN (SELECT value FROM json_each(?)) \
123
         ORDER BY i.updated_ms DESC",
124
    )
125
    .bind(account_id)
126
    .bind(ids_json(repo_ids))
127
    .fetch_all(pool)
128
    .await
129
}