main.rs 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140
  1. #![feature(proc_macro_hygiene, decl_macro)]
  2. #![allow(clippy::new_without_default)]
  3. #[macro_use]
  4. extern crate rocket;
  5. use failure::{Error, ResultExt};
  6. use reqwest::Client;
  7. use rocket::request;
  8. use rocket::State;
  9. use rocket::{http::Status, Outcome, Request};
  10. use std::env;
  11. use std::sync::Arc;
  12. mod handlers;
  13. mod registry;
  14. mod config;
  15. mod github;
  16. mod interactions;
  17. mod payload;
  18. mod team;
  19. use github::{Comment, GithubClient, Issue, User};
  20. use payload::SignedPayload;
  21. use registry::HandleRegistry;
  22. #[derive(PartialEq, Eq, Debug, serde::Deserialize)]
  23. #[serde(rename_all = "lowercase")]
  24. pub enum IssueCommentAction {
  25. Created,
  26. Edited,
  27. Deleted,
  28. }
  29. #[derive(Debug, serde::Deserialize)]
  30. pub struct IssueCommentEvent {
  31. action: IssueCommentAction,
  32. issue: Issue,
  33. comment: Comment,
  34. repository: Repository,
  35. }
  36. #[derive(Debug, serde::Deserialize)]
  37. pub struct Repository {
  38. full_name: String,
  39. }
  40. enum Event {
  41. IssueComment,
  42. Other,
  43. }
  44. impl<'a, 'r> request::FromRequest<'a, 'r> for Event {
  45. type Error = String;
  46. fn from_request(req: &'a Request<'r>) -> request::Outcome<Self, Self::Error> {
  47. let ev = if let Some(ev) = req.headers().get_one("X-GitHub-Event") {
  48. ev
  49. } else {
  50. return Outcome::Failure((Status::BadRequest, "Needs a X-GitHub-Event".into()));
  51. };
  52. let ev = match ev {
  53. "issue_comment" => Event::IssueComment,
  54. _ => Event::Other,
  55. };
  56. Outcome::Success(ev)
  57. }
  58. }
  59. #[derive(Debug)]
  60. struct WebhookError(Error);
  61. impl<'r> rocket::response::Responder<'r> for WebhookError {
  62. fn respond_to(self, _: &Request) -> rocket::response::Result<'r> {
  63. let body = format!("{:?}", self.0);
  64. rocket::Response::build()
  65. .header(rocket::http::ContentType::Plain)
  66. .status(rocket::http::Status::InternalServerError)
  67. .sized_body(std::io::Cursor::new(body))
  68. .ok()
  69. }
  70. }
  71. impl From<Error> for WebhookError {
  72. fn from(e: Error) -> WebhookError {
  73. WebhookError(e)
  74. }
  75. }
  76. #[post("/github-hook", data = "<payload>")]
  77. fn webhook(
  78. event: Event,
  79. payload: SignedPayload,
  80. reg: State<HandleRegistry>,
  81. ) -> Result<(), WebhookError> {
  82. match event {
  83. Event::IssueComment => {
  84. let payload = payload
  85. .deserialize::<IssueCommentEvent>()
  86. .context("IssueCommentEvent failed to deserialize")
  87. .map_err(Error::from)?;
  88. let event = registry::Event::IssueComment(payload);
  89. reg.handle(&event).map_err(Error::from)?;
  90. }
  91. // Other events need not be handled
  92. Event::Other => {}
  93. }
  94. Ok(())
  95. }
  96. #[catch(404)]
  97. fn not_found(_: &Request) -> &'static str {
  98. "Not Found"
  99. }
  100. fn main() {
  101. dotenv::dotenv().ok();
  102. let client = Client::new();
  103. let gh = GithubClient::new(
  104. client.clone(),
  105. env::var("GITHUB_API_TOKEN").expect("Missing GITHUB_API_TOKEN"),
  106. );
  107. let username = Arc::new(User::current(&gh).unwrap().login);
  108. let mut registry = HandleRegistry::new();
  109. handlers::register_all(&mut registry, gh.clone(), username);
  110. let mut config = rocket::Config::active().unwrap();
  111. config.set_port(
  112. env::var("TRIAGEBOT_PORT")
  113. .map(|port| port.parse().unwrap())
  114. .unwrap_or(8000),
  115. );
  116. rocket::custom(config)
  117. .manage(gh)
  118. .manage(registry)
  119. .mount("/", routes![webhook])
  120. .register(catchers![not_found])
  121. .launch();
  122. }