aboutsummaryrefslogtreecommitdiff
path: root/src/query/mod.rs
blob: 4e472c5270a82b3f5062beb7ffde45f18d24f4af (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
use sqlx::prelude::*;
use anyhow::{ Result, Context };

use crate::Conn;

pub mod issues;
pub mod labels;

#[derive(sqlx::FromRow, sqlx::Type)]
pub struct RepositoryInfo {
    pub owner: String,
    pub name: String,
    pub label_count: i64,
    pub issue_count: i64
}

pub async fn repo_id(conn: &mut Conn, owner: &str, name: &str) -> Result<i64> {
    sqlx::query_as::<_, (i64,)>(
        "INSERT OR IGNORE INTO repositories (owner, name) VALUES (?, ?);
         SELECT id FROM repositories WHERE owner = ? AND name = ?"
    ).bind(owner).bind(name)
     .bind(owner).bind(name)
     .fetch_one(conn)
     .await
     .map(|(id,)| id)
     .with_context(|| format!("Couldn't find repo '{}/{}' in database", owner, name))
}

async fn last_updated(conn: &mut Conn, repo: i64) -> Result<Option<i64>> {
    sqlx::query_as::<_, (i64,)>(
        "SELECT MAX(updated_at) FROM issues WHERE repo = ?",
    ).bind(repo)
     .fetch_optional(conn)
     .await
     .map(|opt| opt.map(|row| row.0))
     .with_context(|| format!("Couldn't find time of last update for repo id {}", repo))
}

pub async fn list_repositories(db: &mut Conn) -> sqlx::Result<Vec<RepositoryInfo>> {
    sqlx::query_as(
        "SELECT repositories.owner, repositories.name,
            (SELECT count(id) FROM labels WHERE repo = repositories.id) AS label_count,
            (SELECT count(number) FROM issues WHERE repo = repositories.id) AS issue_count
         FROM repositories"
    ).fetch_all(db)
     .await
}

pub mod graphql {
    use std::time::Duration;
    use reqwest::header;
    use serde::Serialize;
    use futures_retry::{ ErrorHandler, RetryPolicy, FutureRetry };
    use graphql_client::QueryBody;

    static API_ENDPOINT: &str = "https://api.github.com/graphql";
    static USER_AGENT: &str = "github.com/tilpner/github-label-feed";

    static RETRY_DELAY: &[u64] = &[ 5, 50, 250, 1000, 5000, 25000 ];

    pub struct RetryStrategy;
    impl ErrorHandler<reqwest::Error> for RetryStrategy {
        type OutError = reqwest::Error;

        fn handle(&mut self, attempt: usize, e: reqwest::Error) -> RetryPolicy<Self::OutError> {
            match RETRY_DELAY.get(attempt) {
                Some(&ms) => RetryPolicy::WaitRetry(Duration::from_millis(ms)),
                None => RetryPolicy::ForwardError(e)
            }
        }
    }

    pub async fn query(client: &reqwest::Client, api_token: &str, query: QueryBody<impl Serialize>) -> reqwest::Result<reqwest::Response> {
        FutureRetry::new(|| {
            client
                .post(API_ENDPOINT)
                .timeout(Duration::from_secs(60))
                .header(header::USER_AGENT, USER_AGENT)
                .bearer_auth(api_token)
                .json(&query)
                .send()
        }, RetryStrategy)
            .await
            .map(|(res, _)| res)
            .map_err(|(e, _)| e)
    }
}