use crate::api::client::{ApiError, GitHub};
use crate::api::model::{NodeId, RepoId};
use crate::api::queries::{NumberVariables, NumberVariablesFields};
use crate::schema::schema;
#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
pub enum Watched {
Repo(RepoId),
Thread(RepoId, u64),
Discussion(RepoId, u64),
}
impl Watched {
pub fn repo(&self) -> &RepoId {
match self {
Watched::Repo(repo) | Watched::Thread(repo, _) | Watched::Discussion(repo, _) => repo,
}
}
}
impl std::fmt::Display for Watched {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Watched::Repo(repo) => write!(f, "{repo}"),
Watched::Thread(repo, n) | Watched::Discussion(repo, n) => write!(f, "{repo}#{n}"),
}
}
}
#[derive(cynic::Enum, Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum SubscriptionState {
Ignored,
Subscribed,
Unsubscribed,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Subscription {
pub id: NodeId,
pub state: Option<SubscriptionState>,
pub can: bool,
}
impl GitHub {
pub async fn subscription(&self, of: &Watched) -> Result<Subscription, ApiError> {
use cynic::QueryBuilder;
let subject = of.to_string();
let repo = of.repo();
let number = match of {
Watched::Repo(_) => 0,
Watched::Thread(_, n) | Watched::Discussion(_, n) => {
i32::try_from(*n).map_err(|_| ApiError::NotFound(subject.clone()))?
}
};
let vars = NumberVariables {
owner: repo.owner.clone(),
name: repo.name.clone(),
number,
};
match of {
Watched::Repo(_) => {
let op = RepoWatchQuery::build(RepoNameVariables {
owner: vars.owner,
name: vars.name,
});
self.find(op, subject, |q| q.repository.map(|r| r.watch.into()))
.await
}
Watched::Thread(..) => {
let op = ThreadWatchQuery::build(vars);
self.find(op, subject, |q| {
match q.repository?.issue_or_pull_request? {
ThreadWatchOf::Issue(w) => Some(w.into()),
ThreadWatchOf::PullRequest(w) => Some(w.into()),
ThreadWatchOf::Other => None,
}
})
.await
}
Watched::Discussion(..) => {
let op = DiscussionWatchQuery::build(vars);
let pick = |q: DiscussionWatchQuery| Some(q.repository?.discussion?.into());
self.find(op, subject, pick).await
}
}
}
pub async fn subscribe(
&self,
of: &Watched,
id: &NodeId,
state: SubscriptionState,
) -> Result<(), ApiError> {
use cynic::MutationBuilder;
if let Watched::Repo(repo) = of {
let path = format!("/repos/{repo}/subscription");
let (subscribed, ignored) = match state {
SubscriptionState::Unsubscribed => {
return self.rest_write(http::Method::DELETE, &path, None).await;
}
SubscriptionState::Subscribed => (true, false),
SubscriptionState::Ignored => (false, true),
};
let body = serde_json::json!({ "subscribed": subscribed, "ignored": ignored });
return self.rest_write(http::Method::PUT, &path, Some(&body)).await;
}
let op = UpdateSubscription::build(SubscribeVariables {
id: id.gql(),
state,
});
self.mutate(op).await.map(drop)
}
}
macro_rules! watch_fragments {
($($name:ident = $graphql:literal),* $(,)?) => {$(
#[derive(cynic::QueryFragment, Debug)]
#[cynic(graphql_type = $graphql)]
pub struct $name {
pub id: cynic::Id,
pub viewer_subscription: Option<SubscriptionState>,
pub viewer_can_subscribe: bool,
}
impl From<$name> for Subscription {
fn from(w: $name) -> Self {
Subscription {
id: w.id.into(),
state: w.viewer_subscription,
can: w.viewer_can_subscribe,
}
}
}
)*};
}
watch_fragments!(
RepoWatch = "Repository",
IssueWatch = "Issue",
PrWatch = "PullRequest",
DiscussionWatch = "Discussion",
);
#[derive(cynic::QueryVariables, Debug)]
pub struct RepoNameVariables {
pub owner: String,
pub name: String,
}
#[derive(cynic::QueryFragment, Debug)]
#[cynic(graphql_type = "Query", variables = "RepoNameVariables")]
pub struct RepoWatchQuery {
#[arguments(owner: $owner, name: $name)]
pub repository: Option<RepoWatchRepo>,
}
#[derive(cynic::QueryFragment, Debug)]
#[cynic(graphql_type = "Repository")]
pub struct RepoWatchRepo {
#[cynic(spread)]
pub watch: RepoWatch,
}
#[derive(cynic::QueryFragment, Debug)]
#[cynic(graphql_type = "Query", variables = "NumberVariables")]
pub struct ThreadWatchQuery {
#[arguments(owner: $owner, name: $name)]
pub repository: Option<ThreadWatchRepo>,
}
#[derive(cynic::QueryFragment, Debug)]
#[cynic(graphql_type = "Repository", variables = "NumberVariables")]
pub struct ThreadWatchRepo {
#[arguments(number: $number)]
pub issue_or_pull_request: Option<ThreadWatchOf>,
}
#[derive(cynic::InlineFragments, Debug)]
#[cynic(graphql_type = "IssueOrPullRequest")]
pub enum ThreadWatchOf {
Issue(IssueWatch),
PullRequest(PrWatch),
#[cynic(fallback)]
Other,
}
#[derive(cynic::QueryFragment, Debug)]
#[cynic(graphql_type = "Query", variables = "NumberVariables")]
pub struct DiscussionWatchQuery {
#[arguments(owner: $owner, name: $name)]
pub repository: Option<DiscussionWatchRepo>,
}
#[derive(cynic::QueryFragment, Debug)]
#[cynic(graphql_type = "Repository", variables = "NumberVariables")]
pub struct DiscussionWatchRepo {
#[arguments(number: $number)]
pub discussion: Option<DiscussionWatch>,
}
#[derive(cynic::QueryVariables, Debug)]
pub struct SubscribeVariables {
pub id: cynic::Id,
pub state: SubscriptionState,
}
#[derive(cynic::QueryFragment, Debug)]
#[cynic(graphql_type = "UpdateSubscriptionPayload")]
pub struct SubscribePayload {
pub client_mutation_id: Option<String>,
}
#[derive(cynic::QueryFragment, Debug)]
#[cynic(graphql_type = "Mutation", variables = "SubscribeVariables")]
pub struct UpdateSubscription {
#[arguments(input: { subscribableId: $id, state: $state })]
pub update_subscription: Option<SubscribePayload>,
}