Skip to content

Commit

Permalink
Merge pull request #146 from nickelc/notifications
Browse files Browse the repository at this point in the history
Implementation of the Notifications API
  • Loading branch information
softprops committed Aug 18, 2018
2 parents 09c2f97 + 4133c76 commit d2dce6f
Show file tree
Hide file tree
Showing 4 changed files with 369 additions and 0 deletions.
44 changes: 44 additions & 0 deletions examples/notifications.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
extern crate env_logger;
extern crate hubcaps;
extern crate tokio_core;

use std::env;

use tokio_core::reactor::Core;

use hubcaps::notifications::ThreadListOptions;
use hubcaps::{Credentials, Github, Result};

fn main() -> Result<()> {
drop(env_logger::init());
match env::var("GITHUB_TOKEN").ok() {
Some(token) => {
let mut core = Core::new()?;
let github = Github::new(
concat!(env!("CARGO_PKG_NAME"), "/", env!("CARGO_PKG_VERSION")),
Credentials::Token(token),
&core.handle(),
);

let opts = ThreadListOptions::builder().all(true).build();
for thread in core.run(github.activity().notifications().list(&opts))? {
println!("{:#?}", thread);
let subscription = core.run(
github
.activity()
.notifications()
.get_subscription(thread.id),
);
if let Ok(sub) = subscription {
println!("{:#?}", sub);
}
}

// Mark all notifications as read.
core.run(github.activity().notifications().mark_as_read(None))?;

Ok(())
}
_ => Err("example missing GITHUB_TOKEN".into()),
}
}
7 changes: 7 additions & 0 deletions src/activity/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
use hyper::client::Connect;

use Github;
use notifications::Notifications;
use stars::Stars;

pub struct Activity<C>
Expand All @@ -17,6 +18,12 @@ impl<C: Clone + Connect> Activity<C> {
pub fn new(github: Github<C>) -> Self {
Self { github }
}

/// Return a reference to notifications operations
pub fn notifications(&self) -> Notifications<C> {
Notifications::new(self.github.clone())
}

/// return a reference to starring operations
pub fn stars(&self) -> Stars<C> {
Stars::new(self.github.clone())
Expand Down
8 changes: 8 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ pub mod hooks;
pub mod issues;
pub mod keys;
pub mod labels;
pub mod notifications;
pub mod organizations;
pub mod pull_commits;
pub mod pulls;
Expand Down Expand Up @@ -533,6 +534,13 @@ where
)
}

fn patch_no_response(&self, uri: &str, message: Vec<u8>) -> Future<()> {
Box::new(self.patch(uri, message).or_else(|err| match err {
Error(ErrorKind::Codec(_), _) => Ok(()),
err => Err(err),
}))
}

fn patch_media<D>(&self, uri: &str, message: Vec<u8>, media: MediaType) -> Future<D>
where
D: DeserializeOwned + 'static,
Expand Down
310 changes: 310 additions & 0 deletions src/notifications/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,310 @@
//! Notifications interface
extern crate serde_json;

use std::collections::HashMap;

use futures::future;
use hyper::client::Connect;
use url::form_urlencoded;

use users::User;
use Future;
use Github;

/// Provides access to notifications.
/// See the [github docs](https://developer.github.com/v3/activity/notifications/)
/// for more information.
pub struct Notifications<C>
where
C: Clone + Connect,
{
github: Github<C>,
}

impl<C: Clone + Connect> Notifications<C> {
#[doc(hidden)]
pub fn new(github: Github<C>) -> Self {
Self { github }
}

/// List the authenticated user's notifications.
///
/// See the [github docs](https://developer.github.com/v3/activity/notifications/#list-your-notifications)
/// for more information.
pub fn list(&self, options: &ThreadListOptions) -> Future<Vec<Thread>> {
let mut uri = vec!["/notifications".into()];
if let Some(query) = options.serialize() {
uri.push(query);
}
self.github.get(&uri.join("?"))
}

/// List the authenticated user's notifications for a repository.
///
/// See the [github docs](https://developer.github.com/v3/activity/notifications/#list-your-notifications-in-a-repository)
/// for more information.
pub fn list_for_repo<O, R>(
&self,
owner: O,
repo: R,
options: &ThreadListOptions,
) -> Future<Vec<Thread>>
where
O: Into<String>,
R: Into<String>,
{
let mut uri = vec![format!(
"/repos/{}/{}/notifications",
owner.into(),
repo.into()
)];
if let Some(query) = options.serialize() {
uri.push(query);
}
self.github.get(&uri.join("?"))
}

/// Mark notifications as read. Default: `now`
///
/// See the [github docs](https://developer.github.com/v3/activity/notifications/#mark-as-read)
/// for more information.
pub fn mark_as_read<S>(&self, last_read_at: S) -> Future<()>
where
S: Into<Option<String>>,
{
let url = match last_read_at.into() {
Some(last_read_at) => format!(
"/notifications?{}",
form_urlencoded::Serializer::new(String::new())
.append_pair("last_read_at", &last_read_at)
.finish()
),
None => String::from("/notifications"),
};
self.github.put_no_response(&url, Vec::new())
}

/// Mark notifications as read in a repository. Default: `now`
///
/// See [github docs](https://developer.github.com/v3/activity/notifications/#mark-notifications-as-read-in-a-repository)
/// for more information.
pub fn mark_as_read_for_repo<O, R, S>(&self, owner: O, repo: R, last_read_at: S) -> Future<()>
where
O: Into<String>,
R: Into<String>,
S: Into<Option<String>>,
{
let path = match last_read_at.into() {
Some(last_read_at) => format!(
"/notifications?{}",
form_urlencoded::Serializer::new(String::new())
.append_pair("last_read_at", &last_read_at)
.finish()
),
None => String::from("/notifications"),
};
self.github.put_no_response(
&format!("/repos/{}/{}{}", owner.into(), repo.into(), path),
Vec::new(),
)
}

/// Return a single thread.
///
/// See the [github docs](https://developer.github.com/v3/activity/notifications/#view-a-single-thread)
/// for more information.
pub fn get_thread<S>(&self, id: S) -> Future<Thread>
where
S: Into<String>,
{
self.github
.get(&format!("/notifications/threads/{}", id.into()))
}

/// Mark a thread as read
///
/// See the [github docs](https://developer.github.com/v3/activity/notifications/#mark-a-thread-as-read)
/// for more information.
pub fn mark_thread_as_read<S>(&self, id: S) -> Future<()>
where
S: Into<String>,
{
self.github
.patch_no_response(&format!("/notifications/threads/{}", id.into()), Vec::new())
}

/// Return the subscription information for a thread.
///
/// See the [github docs](https://developer.github.com/v3/activity/notifications/#get-a-thread-subscription)
/// for more information.
pub fn get_subscription<S>(&self, id: S) -> Future<Subscription>
where
S: Into<String>,
{
self.github.get(&format!(
"/notifications/threads/{}/subscription",
id.into(),
))
}

/// Subscribe to a thread and return the subscription information.
///
/// See the [github docs](https://developer.github.com/v3/activity/notifications/#set-a-thread-subscription)
/// for more information.
pub fn subscribe<S>(&self, id: S) -> Future<Subscription>
where
S: Into<String>,
{
let mut map = HashMap::new();
map.insert("subscribed", true);

self.github.put(
&format!("/notifications/threads/{}/subscription", id.into()),
json!(map),
)
}

/// Unsubscribe to a thread and return the subscription information.
///
/// See the [github docs](https://developer.github.com/v3/activity/notifications/#set-a-thread-subscription)
/// for more information.
pub fn unsubscribe<S>(&self, id: S) -> Future<Subscription>
where
S: Into<String>,
{
let mut map = HashMap::new();
map.insert("ignored", true);

self.github.put(
&format!("/notifications/threads/{}/subscription", id.into()),
json!(map),
)
}

/// Delete the thread subscription.
///
/// See the [github docs](https://developer.github.com/v3/activity/notifications/#delete-a-thread-subscription)
/// for more information.
pub fn delete_subscription<S>(&self, id: S) -> Future<()>
where
S: Into<String>,
{
self.github.delete(&format!(
"/notifications/threads/{}/subscription",
id.into()
))
}
}

// representations

#[derive(Debug, Deserialize)]
pub struct Thread {
pub id: String,
pub unread: bool,
pub updated_at: String,
pub last_read_at: Option<String>,
pub reason: String,
pub subject: Subject,
pub repository: Repository,
pub url: String,
pub subscription_url: String,
}

#[derive(Default)]
pub struct ThreadListOptions {
params: HashMap<&'static str, String>,
}

impl ThreadListOptions {
pub fn builder() -> ThreadListOptionsBuilder {
ThreadListOptionsBuilder::new()
}

/// serialize options as a string. returns None if no options are defined
pub fn serialize(&self) -> Option<String> {
if self.params.is_empty() {
None
} else {
let encoded: String = form_urlencoded::Serializer::new(String::new())
.extend_pairs(&self.params)
.finish();
Some(encoded)
}
}
}

#[derive(Default)]
pub struct ThreadListOptionsBuilder(ThreadListOptions);

impl ThreadListOptionsBuilder {
pub fn new() -> Self {
Default::default()
}

/// if `true`, show notifications marked as read. Default: `false`
pub fn all(&mut self, all: bool) -> &mut Self {
self.0.params.insert("all", all.to_string());
self
}

/// if `true`, only shows notifications in which the user is directly participating or
/// mentioned. Default: `false`
pub fn participating(&mut self, val: bool) -> &mut Self {
self.0.params.insert("participating", val.to_string());
self
}

/// Only show notifications updated after the given time.
pub fn since<T>(&mut self, since: T) -> &mut Self
where
T: Into<String>,
{
self.0.params.insert("since", since.into());
self
}

/// Only show notifications updated before a given time.
pub fn before<T>(&mut self, before: T) -> &mut Self
where
T: Into<String>,
{
self.0.params.insert("before", before.into());
self
}

pub fn build(&self) -> ThreadListOptions {
ThreadListOptions {
params: self.0.params.clone(),
}
}
}

#[derive(Debug, Deserialize)]
pub struct Subject {
title: String,
url: String,
latest_comment_url: String,
#[serde(rename = "type")]
kind: String,
}

#[derive(Debug, Deserialize)]
pub struct Repository {
pub id: u32,
pub node_id: String,
pub name: String,
pub full_name: String,
pub owner: User,
pub html_url: String,
}

#[derive(Debug, Deserialize)]
pub struct Subscription {
pub subscribed: bool,
pub ignored: bool,
pub reason: String,
pub created_at: String,
pub url: String,
pub thread_url: String,
}

0 comments on commit d2dce6f

Please sign in to comment.