2020-09-10 21:33:34 +02:00
|
|
|
/* This file is part of issue-bot.
|
|
|
|
*
|
|
|
|
* issue-bot is free software: you can redistribute it and/or modify
|
|
|
|
* it under the terms of the GNU General Public License as published by
|
|
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
|
|
* (at your option) any later version.
|
|
|
|
*
|
|
|
|
* issue-bot is distributed in the hope that it will be useful,
|
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
* GNU General Public License for more details.
|
|
|
|
*
|
|
|
|
* You should have received a copy of the GNU General Public License
|
|
|
|
* along with issue-bot. If not, see <https://www.gnu.org/licenses/>.
|
|
|
|
*/
|
|
|
|
|
2019-09-20 11:59:13 +02:00
|
|
|
use super::*;
|
|
|
|
|
2022-09-24 14:30:31 +02:00
|
|
|
#[macro_export]
|
|
|
|
macro_rules! gitea_api_mismatch {
|
|
|
|
($map:ident[$value:literal].$conv_method:ident()) => {{
|
|
|
|
$map[$value].$conv_method().ok_or_else(|| {
|
|
|
|
log::error!(
|
|
|
|
"issue API response missing valid {} field: {:?}",
|
|
|
|
$value,
|
|
|
|
$map
|
|
|
|
);
|
|
|
|
Error::new("Gitea API response or API version not matching what was expected.")
|
|
|
|
})?
|
|
|
|
}};
|
|
|
|
}
|
|
|
|
|
|
|
|
static ISSUES_BASE_URL: &str = "{base_url}/api/v1/repos/{repo}/issues";
|
|
|
|
static ISSUES_COMMENTS_URL: &str = "{base_url}/api/v1/repos/{repo}/issues/{index}/comments";
|
2019-09-20 11:59:13 +02:00
|
|
|
|
|
|
|
use serde::Serialize;
|
|
|
|
|
|
|
|
#[derive(Serialize, Default)]
|
|
|
|
struct CreateIssueOption {
|
|
|
|
assignee: String,
|
|
|
|
assignees: Vec<String>,
|
|
|
|
body: String,
|
|
|
|
closed: bool,
|
|
|
|
title: String,
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn new_issue(
|
|
|
|
conn: &Connection,
|
|
|
|
title: String,
|
|
|
|
body: String,
|
|
|
|
anonymous: bool,
|
|
|
|
submitter: Address,
|
2020-09-10 21:33:34 +02:00
|
|
|
conf: &Configuration,
|
2019-09-20 11:59:13 +02:00
|
|
|
) -> Result<(Password, i64)> {
|
|
|
|
let issue = CreateIssueOption {
|
|
|
|
title,
|
|
|
|
body: format!(
|
|
|
|
"{} reports:\n\n{}",
|
|
|
|
if anonymous {
|
|
|
|
"Anonymous".to_string()
|
|
|
|
} else {
|
|
|
|
submitter.to_string()
|
|
|
|
},
|
|
|
|
body
|
|
|
|
),
|
|
|
|
..CreateIssueOption::default()
|
|
|
|
};
|
2022-09-24 14:30:31 +02:00
|
|
|
let client = reqwest::blocking::Client::new();
|
2019-09-20 11:59:13 +02:00
|
|
|
let res = client
|
|
|
|
.post(
|
|
|
|
&ISSUES_BASE_URL
|
|
|
|
.replace("{base_url}", &conf.base_url)
|
|
|
|
.replace("{repo}", &conf.repo),
|
|
|
|
)
|
2019-09-29 15:36:14 +02:00
|
|
|
.header("Authorization", format!("token {}", &conf.auth_token))
|
2019-09-20 11:59:13 +02:00
|
|
|
.json(&issue)
|
2022-09-24 14:30:31 +02:00
|
|
|
.send()?
|
|
|
|
.text()?;
|
2019-09-20 11:59:13 +02:00
|
|
|
|
2022-09-24 14:30:31 +02:00
|
|
|
let map: serde_json::map::Map<String, serde_json::Value> = serde_json::from_str(&res)?;
|
2019-09-20 11:59:13 +02:00
|
|
|
let issue = Issue {
|
2022-09-24 14:30:31 +02:00
|
|
|
id: gitea_api_mismatch!(map["number"].as_i64()),
|
2019-09-20 11:59:13 +02:00
|
|
|
submitter,
|
|
|
|
password: Uuid::new_v4(),
|
2022-09-24 14:30:31 +02:00
|
|
|
time_created: chrono::Local::now().to_rfc3339_opts(chrono::SecondsFormat::Millis, true),
|
2019-09-20 11:59:13 +02:00
|
|
|
anonymous,
|
|
|
|
subscribed: true,
|
|
|
|
title: issue.title,
|
2022-09-24 14:30:31 +02:00
|
|
|
last_update: gitea_api_mismatch!(map["created_at"].as_str()).to_string(),
|
2019-09-20 11:59:13 +02:00
|
|
|
};
|
|
|
|
conn.execute(
|
|
|
|
"INSERT INTO issue (id, submitter, password, time_created, anonymous, subscribed, title, last_update)
|
|
|
|
VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8)",
|
|
|
|
&[
|
|
|
|
&issue.id,
|
|
|
|
&issue.submitter.to_string() as &dyn ToSql,
|
|
|
|
&issue.password.as_bytes().to_vec(),
|
|
|
|
&issue.time_created,
|
|
|
|
&issue.anonymous,
|
|
|
|
&issue.subscribed,
|
|
|
|
&issue.title,
|
|
|
|
&issue.last_update,
|
|
|
|
],
|
2022-09-24 14:30:31 +02:00
|
|
|
)?;
|
2019-09-20 11:59:13 +02:00
|
|
|
Ok((issue.password, issue.id))
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Serialize, Default)]
|
|
|
|
struct CreateIssueCommentOption {
|
|
|
|
body: String,
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn new_reply(
|
|
|
|
conn: &Connection,
|
|
|
|
body: String,
|
|
|
|
password: Password,
|
|
|
|
submitter: Address,
|
2020-09-10 21:33:34 +02:00
|
|
|
conf: &Configuration,
|
2019-09-20 11:59:13 +02:00
|
|
|
) -> Result<(String, i64, bool)> {
|
|
|
|
let mut stmt =
|
|
|
|
conn.prepare("SELECT id, title, subscribed, anonymous FROM issue WHERE password = ?")?;
|
|
|
|
let mut results = stmt
|
2022-09-24 14:30:31 +02:00
|
|
|
.query_map([password.as_bytes().to_vec()], |row| {
|
2019-09-20 11:59:13 +02:00
|
|
|
Ok((row.get(0)?, row.get(1)?, row.get(2)?, row.get(3)?))
|
|
|
|
})?
|
2022-09-24 14:30:31 +02:00
|
|
|
.collect::<std::result::Result<Vec<(i64, String, bool, bool)>, _>>()?;
|
2019-09-20 11:59:13 +02:00
|
|
|
if results.is_empty() {
|
2020-09-10 21:33:34 +02:00
|
|
|
return Err(Error::new("Not found".to_string()));
|
2019-09-20 11:59:13 +02:00
|
|
|
}
|
2022-09-24 14:30:31 +02:00
|
|
|
let client = reqwest::blocking::Client::new();
|
2019-09-20 11:59:13 +02:00
|
|
|
let response = client
|
|
|
|
.post(
|
|
|
|
&ISSUES_COMMENTS_URL
|
|
|
|
.replace("{base_url}", &conf.base_url)
|
|
|
|
.replace("{repo}", &conf.repo)
|
|
|
|
.replace("{index}", &results[0].0.to_string()),
|
|
|
|
)
|
2019-09-29 15:36:14 +02:00
|
|
|
.header("Authorization", format!("token {}", &conf.auth_token))
|
2019-09-20 11:59:13 +02:00
|
|
|
.json(&CreateIssueCommentOption {
|
|
|
|
body: format!(
|
|
|
|
"{} replies:\n\n{}",
|
|
|
|
if results[0].3 {
|
|
|
|
"Anonymous".to_string()
|
|
|
|
} else {
|
|
|
|
submitter.to_string()
|
|
|
|
},
|
|
|
|
body
|
|
|
|
),
|
|
|
|
})
|
|
|
|
.send()?;
|
|
|
|
if response.status().is_success() {
|
|
|
|
let (issue_id, title, is_subscribed, _) = results.remove(0);
|
|
|
|
Ok((title, issue_id, is_subscribed))
|
|
|
|
} else {
|
|
|
|
eprintln!(
|
|
|
|
"New reply could not be created: {:?}\npassword: {}\nsubmitter: {}\nbody: {}",
|
|
|
|
response.status(),
|
2022-09-24 14:30:31 +02:00
|
|
|
password,
|
|
|
|
submitter,
|
2019-09-20 11:59:13 +02:00
|
|
|
body
|
|
|
|
);
|
2020-09-10 21:33:34 +02:00
|
|
|
Err(Error::new(
|
2019-09-20 11:59:13 +02:00
|
|
|
"You can not reply to this issue due to an internal error.",
|
|
|
|
))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Serialize, Default)]
|
|
|
|
struct EditIssueOption {
|
|
|
|
state: String,
|
|
|
|
}
|
|
|
|
|
2020-09-10 21:33:34 +02:00
|
|
|
pub fn close(
|
|
|
|
conn: &Connection,
|
|
|
|
password: Password,
|
|
|
|
conf: &Configuration,
|
|
|
|
) -> Result<(String, i64, bool)> {
|
2019-09-20 11:59:13 +02:00
|
|
|
let mut stmt = conn.prepare("SELECT id, title, subscribed FROM issue WHERE password = ?")?;
|
|
|
|
let mut results = stmt
|
2022-09-24 14:30:31 +02:00
|
|
|
.query_map([password.as_bytes().to_vec()], |row| {
|
2019-09-20 11:59:13 +02:00
|
|
|
Ok((row.get(0)?, row.get(1)?, row.get(2)?))
|
|
|
|
})?
|
2022-09-24 14:30:31 +02:00
|
|
|
.collect::<std::result::Result<Vec<(i64, String, bool)>, _>>()?;
|
2019-09-20 11:59:13 +02:00
|
|
|
if results.is_empty() {
|
2020-09-10 21:33:34 +02:00
|
|
|
return Err(Error::new("Not found".to_string()));
|
2019-09-20 11:59:13 +02:00
|
|
|
}
|
2022-09-24 14:30:31 +02:00
|
|
|
let client = reqwest::blocking::Client::new();
|
2019-09-20 11:59:13 +02:00
|
|
|
let res = client
|
|
|
|
.patch(&format!(
|
|
|
|
"{}/{}",
|
|
|
|
ISSUES_BASE_URL
|
|
|
|
.replace("{base_url}", &conf.base_url)
|
|
|
|
.replace("{repo}", &conf.repo),
|
|
|
|
&results[0].0.to_string()
|
|
|
|
))
|
2019-09-29 15:36:14 +02:00
|
|
|
.header("Authorization", format!("token {}", &conf.auth_token))
|
2019-09-20 11:59:13 +02:00
|
|
|
.json(&EditIssueOption {
|
|
|
|
state: "closed".to_string(),
|
|
|
|
})
|
|
|
|
.send()?
|
|
|
|
.text()?;
|
|
|
|
|
2022-09-24 14:30:31 +02:00
|
|
|
let map: serde_json::map::Map<String, serde_json::Value> = serde_json::from_str(&res)?;
|
2019-09-20 11:59:13 +02:00
|
|
|
if map["state"] == "closed" {
|
|
|
|
let (issue_id, title, is_subscribed) = results.remove(0);
|
|
|
|
Ok((title, issue_id, is_subscribed))
|
|
|
|
} else {
|
|
|
|
eprintln!("Issue could not be closed: {:#?}", map);
|
2020-09-10 21:33:34 +02:00
|
|
|
Err(Error::new(
|
2019-09-20 11:59:13 +02:00
|
|
|
"Issue cannot be closed due to an internal error.",
|
|
|
|
))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn change_subscription(
|
|
|
|
conn: &Connection,
|
|
|
|
password: Password,
|
|
|
|
new_val: bool,
|
|
|
|
) -> Result<(String, i64, bool)> {
|
|
|
|
let mut stmt = conn.prepare("SELECT id, title, subscribed FROM issue WHERE password = ?")?;
|
2022-09-24 14:30:31 +02:00
|
|
|
let mut results: Vec<(i64, String, bool)> = stmt
|
|
|
|
.query_map([password.as_bytes().to_vec()], |row| {
|
2019-09-20 11:59:13 +02:00
|
|
|
Ok((row.get(0)?, row.get(1)?, row.get(2)?))
|
|
|
|
})?
|
2022-09-24 14:30:31 +02:00
|
|
|
.collect::<std::result::Result<Vec<(i64, String, bool)>, _>>()?;
|
2019-09-20 11:59:13 +02:00
|
|
|
if results.is_empty() {
|
2020-09-10 21:33:34 +02:00
|
|
|
return Err(Error::new("Issue not found".to_string()));
|
2019-09-20 11:59:13 +02:00
|
|
|
}
|
|
|
|
let (issue_id, title, is_subscribed) = results.remove(0);
|
|
|
|
if !is_subscribed && !new_val {
|
2020-09-10 21:33:34 +02:00
|
|
|
return Err(Error::new(format!(
|
2019-09-20 11:59:13 +02:00
|
|
|
"You are not subscribed to issue `{}`",
|
|
|
|
&title
|
|
|
|
)));
|
|
|
|
} else if is_subscribed && new_val {
|
2020-09-10 21:33:34 +02:00
|
|
|
return Err(Error::new(format!(
|
2019-09-20 11:59:13 +02:00
|
|
|
"You are already subscribed to issue `{}`",
|
|
|
|
&title
|
|
|
|
)));
|
|
|
|
}
|
|
|
|
|
|
|
|
let mut stmt =
|
|
|
|
conn.prepare("UPDATE issue SET subscribed = (:subscribed) WHERE password = (:password)")?;
|
|
|
|
assert_eq!(
|
2022-09-24 14:30:31 +02:00
|
|
|
stmt.execute(rusqlite::named_params! {
|
|
|
|
":subscribed": &new_val,
|
|
|
|
":password": &password.as_bytes().to_vec()
|
|
|
|
})?,
|
2019-09-20 11:59:13 +02:00
|
|
|
1
|
|
|
|
);
|
|
|
|
Ok((title, issue_id, is_subscribed))
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn comments(
|
|
|
|
id: i64,
|
|
|
|
since: &str,
|
2020-09-10 21:33:34 +02:00
|
|
|
conf: &Configuration,
|
2022-09-24 14:30:31 +02:00
|
|
|
) -> Result<Vec<serde_json::map::Map<String, serde_json::Value>>> {
|
|
|
|
let client = reqwest::blocking::Client::new();
|
2019-09-20 11:59:13 +02:00
|
|
|
let result = client
|
|
|
|
.get(
|
|
|
|
&ISSUES_COMMENTS_URL
|
|
|
|
.replace("{base_url}", &conf.base_url)
|
|
|
|
.replace("{repo}", &conf.repo)
|
|
|
|
.replace("{index}", &id.to_string()),
|
|
|
|
)
|
|
|
|
.query(&[("since", since)])
|
2022-09-24 14:30:31 +02:00
|
|
|
.send()?
|
|
|
|
.text()?;
|
|
|
|
let result: Vec<_> = serde_json::from_str(&result)?;
|
|
|
|
Ok(result)
|
2019-09-20 11:59:13 +02:00
|
|
|
}
|