Compare commits
10 Commits
0192b92ae9
...
a4d99562c3
Author | SHA1 | Date |
---|---|---|
Franklin | a4d99562c3 | |
Franklin | 6c337448f3 | |
Franklin | e6780761e9 | |
Franklin | a9b5f28d3e | |
Franklin | 751eb5237c | |
Franklin | a058023e05 | |
bni_admin | 8ba7775bcf | |
Franklin | e271f0e344 | |
Franklin | efacc602af | |
Franklin | b97d0e8bec |
|
@ -183,11 +183,11 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "actix-web-utils"
|
name = "actix-web-utils"
|
||||||
version = "0.2.5"
|
version = "0.2.21"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "git+https://github.com/franklinblanco/actix-web-utils.git#416e44e849a4911fde589424a703236585192365"
|
||||||
checksum = "71f969d27d80936f296f39a96c4f1c9c9012b06a76fd6879f3ea65d43cb86f44"
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"actix-web",
|
"actix-web",
|
||||||
|
"err",
|
||||||
"log",
|
"log",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
|
@ -212,9 +212,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "aho-corasick"
|
name = "aho-corasick"
|
||||||
version = "0.7.18"
|
version = "0.7.19"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f"
|
checksum = "b4f55bd91a0978cbfd91c457a164bab8b4001c833b7f323132c0a4e1922dd44e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"memchr",
|
"memchr",
|
||||||
]
|
]
|
||||||
|
@ -263,9 +263,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "block-buffer"
|
name = "block-buffer"
|
||||||
version = "0.10.2"
|
version = "0.10.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0bf7fe51849ea569fd452f37822f606a5cabb684dc918707a0193fd4664ff324"
|
checksum = "69cce20737498f97b993470a6e536b8523f0af7892a4f928cceb1ac5e52ebe7e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"generic-array",
|
"generic-array",
|
||||||
]
|
]
|
||||||
|
@ -378,9 +378,9 @@ checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cpufeatures"
|
name = "cpufeatures"
|
||||||
version = "0.2.4"
|
version = "0.2.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "dc948ebb96241bb40ab73effeb80d9f93afaad49359d159a5e61be51619fe813"
|
checksum = "28d997bd5e24a5928dd43e46dc529867e207907fe0b239c3477d924f7f2ca320"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
@ -423,7 +423,7 @@ version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"actix-web-utils",
|
"actix-web-utils",
|
||||||
"dev-dtos",
|
"dev-dtos",
|
||||||
"openssl",
|
"err",
|
||||||
"reqwest",
|
"reqwest",
|
||||||
"serde",
|
"serde",
|
||||||
"tokio",
|
"tokio",
|
||||||
|
@ -432,7 +432,7 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "dev-dtos"
|
name = "dev-dtos"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://backend:Eo1n1TPsyWV7wwo9uFwgUJGKKheMM6paM2mDkVPA4zqkh5dt6Q6XPkbtojzYQudQsM84vSwKmhHHTPjyn535d6NLBmA3meeGj0Gb8if4sceAwvySdmzedg5mN2P5zzQt@gitea.blancoinfante.com/blancoinfante_backend/dev-dtos-rust.git#80abf74269baf13b9fc1d186516564b0d4521270"
|
source = "git+https://github.com/franklinblanco/user-dtos.git#3b3ffde695753edb4e7ffdf722299b372f2c0bd0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"chrono",
|
"chrono",
|
||||||
"serde",
|
"serde",
|
||||||
|
@ -457,6 +457,14 @@ dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "err"
|
||||||
|
version = "0.1.1"
|
||||||
|
source = "git+https://github.com/franklinblanco/err.git#42237f4a4be71530933f920f7e5eb6dbf1d0c75e"
|
||||||
|
dependencies = [
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "fastrand"
|
name = "fastrand"
|
||||||
version = "1.8.0"
|
version = "1.8.0"
|
||||||
|
@ -528,6 +536,12 @@ version = "0.3.24"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4e5aa3de05362c3fb88de6531e6296e85cde7739cccad4b9dfeeb7f6ebce56bf"
|
checksum = "4e5aa3de05362c3fb88de6531e6296e85cde7739cccad4b9dfeeb7f6ebce56bf"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "futures-io"
|
||||||
|
version = "0.3.24"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "bbf4d2a7a308fd4578637c0b17c7e1c7ba127b8f6ba00b29f717e9655d85eb68"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "futures-sink"
|
name = "futures-sink"
|
||||||
version = "0.3.24"
|
version = "0.3.24"
|
||||||
|
@ -547,9 +561,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "44fb6cb1be61cc1d2e43b262516aafcf63b241cffdb1d3fa115f91d9c7b09c90"
|
checksum = "44fb6cb1be61cc1d2e43b262516aafcf63b241cffdb1d3fa115f91d9c7b09c90"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"futures-core",
|
"futures-core",
|
||||||
|
"futures-io",
|
||||||
"futures-task",
|
"futures-task",
|
||||||
|
"memchr",
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
"pin-utils",
|
"pin-utils",
|
||||||
|
"slab",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -828,9 +845,9 @@ checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "miniz_oxide"
|
name = "miniz_oxide"
|
||||||
version = "0.5.3"
|
version = "0.5.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6f5c75688da582b8ffc1f1799e9db273f32133c49e048f614d22ec3256773ccc"
|
checksum = "96590ba8f175222643a85693f33d26e9c8a015f599c216509b1a6894af675d34"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"adler",
|
"adler",
|
||||||
]
|
]
|
||||||
|
@ -905,9 +922,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "once_cell"
|
name = "once_cell"
|
||||||
version = "1.13.1"
|
version = "1.14.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "074864da206b4973b84eb91683020dbefd6a8c3f0f38e054d93954e891935e4e"
|
checksum = "2f7254b99e31cad77da24b08ebf628882739a608578bb1bcdfc1f9c21260d7c0"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "openssl"
|
name = "openssl"
|
||||||
|
@ -941,15 +958,6 @@ version = "0.1.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf"
|
checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "openssl-src"
|
|
||||||
version = "111.22.0+1.1.1q"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "8f31f0d509d1c1ae9cada2f9539ff8f37933831fd5098879e482aa687d659853"
|
|
||||||
dependencies = [
|
|
||||||
"cc",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "openssl-sys"
|
name = "openssl-sys"
|
||||||
version = "0.9.75"
|
version = "0.9.75"
|
||||||
|
@ -959,7 +967,6 @@ dependencies = [
|
||||||
"autocfg",
|
"autocfg",
|
||||||
"cc",
|
"cc",
|
||||||
"libc",
|
"libc",
|
||||||
"openssl-src",
|
|
||||||
"pkg-config",
|
"pkg-config",
|
||||||
"vcpkg",
|
"vcpkg",
|
||||||
]
|
]
|
||||||
|
@ -1248,9 +1255,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "sha1"
|
name = "sha1"
|
||||||
version = "0.10.2"
|
version = "0.10.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5cf2781a4ca844dd4f9b608a1791eea19830df0ad3cdd9988cd05f1c66ccb63a"
|
checksum = "006769ba83e921b3085caa8334186b00cf92b4cb1a6cf4632fbccc8eff5c7549"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"cpufeatures",
|
"cpufeatures",
|
||||||
|
@ -1362,9 +1369,9 @@ checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tokio"
|
name = "tokio"
|
||||||
version = "1.20.1"
|
version = "1.21.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7a8325f63a7d4774dd041e363b2409ed1c5cbbd0f867795e661df066b2b0a581"
|
checksum = "89797afd69d206ccd11fb0ea560a44bbb87731d020670e79416d442919257d42"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"autocfg",
|
"autocfg",
|
||||||
"bytes",
|
"bytes",
|
||||||
|
|
|
@ -8,9 +8,10 @@ edition = "2021"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
tokio = { version = "1.20.1", features = ["full"] }
|
tokio = { version = "1.20.1", features = ["full"] }
|
||||||
reqwest = { version = "0.11.11", features = [ "json" ]}
|
reqwest = { version = "0.11.11", features = [ "json", "blocking" ]}
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
actix-web-utils = "0.2.5"
|
|
||||||
|
|
||||||
openssl = { version = "0.10", features = ["vendored"] }
|
#openssl = { version = "0.10", features = ["vendored"] }
|
||||||
dev-dtos = { git = "https://backend:Eo1n1TPsyWV7wwo9uFwgUJGKKheMM6paM2mDkVPA4zqkh5dt6Q6XPkbtojzYQudQsM84vSwKmhHHTPjyn535d6NLBmA3meeGj0Gb8if4sceAwvySdmzedg5mN2P5zzQt@gitea.blancoinfante.com/blancoinfante_backend/dev-dtos-rust.git" }
|
err = { git = "https://github.com/franklinblanco/err.git" }
|
||||||
|
dev-dtos = { git = "https://github.com/franklinblanco/user-svc-dtos-rust.git" }
|
||||||
|
actix-web-utils = { git = "https://github.com/franklinblanco/actix-web-utils.git" }
|
|
@ -1,4 +1,5 @@
|
||||||
use actix_web_utils::{enums::error::Error, dtos::message::MessageResource};
|
use err::{Error, MessageResource};
|
||||||
|
use reqwest::Client;
|
||||||
use serde::{Serialize, de::DeserializeOwned};
|
use serde::{Serialize, de::DeserializeOwned};
|
||||||
|
|
||||||
pub async fn perform_request<B: Serialize, R: DeserializeOwned>(
|
pub async fn perform_request<B: Serialize, R: DeserializeOwned>(
|
||||||
|
@ -30,7 +31,7 @@ pub async fn perform_request<B: Serialize, R: DeserializeOwned>(
|
||||||
true => {
|
true => {
|
||||||
match res.json::<R>().await {
|
match res.json::<R>().await {
|
||||||
Ok(resp_dto) => Ok(resp_dto), // Return correctly deserialized obj
|
Ok(resp_dto) => Ok(resp_dto), // Return correctly deserialized obj
|
||||||
Err(err) => Err(Error::ClientError(MessageResource::new_from_err(err))),
|
Err(err) => Err(Error::Serde(MessageResource::from(err))),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
false => {
|
false => {
|
||||||
|
@ -38,14 +39,122 @@ pub async fn perform_request<B: Serialize, R: DeserializeOwned>(
|
||||||
Err(Error::UnexpectedStatusCode(
|
Err(Error::UnexpectedStatusCode(
|
||||||
expected_status_code,
|
expected_status_code,
|
||||||
res.status().as_u16(),
|
res.status().as_u16(),
|
||||||
MessageResource::new_from_str(&res.text().await.unwrap()),
|
match res.json::<Vec<MessageResource>>().await {
|
||||||
|
Ok(messages) => messages,
|
||||||
|
Err(e) => vec![MessageResource::from(e)],
|
||||||
|
},
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
// Request couldn't be sent
|
// Request couldn't be sent
|
||||||
Err(Error::ClientError(MessageResource::new_from_err(e)))
|
Err(Error::Network(MessageResource::from(e)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// This function is mainly for when you don't have a client in your application and just want to get it over with.
|
||||||
|
/// This shouldn't be used as it takes more resource consumption than the above method.
|
||||||
|
pub async fn perform_request_without_client<B: Serialize, R: DeserializeOwned>(
|
||||||
|
base_url: String,
|
||||||
|
method: reqwest::Method,
|
||||||
|
path: String,
|
||||||
|
body: Option<B>,
|
||||||
|
expected_status_code: u16,
|
||||||
|
headers: Vec<(String, String)>,
|
||||||
|
) -> Result<R, Error> {
|
||||||
|
let client = Client::new();
|
||||||
|
let mut req_incomplete =
|
||||||
|
client.request(method, format!("{url}{path}", url = base_url, path = path));
|
||||||
|
|
||||||
|
for header in headers {
|
||||||
|
req_incomplete = req_incomplete.header(&header.0, &header.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
let req_complete = match body {
|
||||||
|
Some(b) => req_incomplete.json(&b),
|
||||||
|
None => req_incomplete.header("content-length", 0),
|
||||||
|
};
|
||||||
|
println!("{:?}", req_complete);
|
||||||
|
match req_complete.send().await {
|
||||||
|
// Error handling here
|
||||||
|
Ok(res) => {
|
||||||
|
// Request sent correctly
|
||||||
|
match res.status().as_u16() == expected_status_code {
|
||||||
|
true => {
|
||||||
|
match res.json::<R>().await {
|
||||||
|
Ok(resp_dto) => Ok(resp_dto), // Return correctly deserialized obj
|
||||||
|
Err(err) => Err(Error::Serde(MessageResource::from(err))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
false => {
|
||||||
|
//If status code is any other than expected
|
||||||
|
Err(Error::UnexpectedStatusCode(
|
||||||
|
expected_status_code,
|
||||||
|
res.status().as_u16(),
|
||||||
|
match res.json::<Vec<MessageResource>>().await {
|
||||||
|
Ok(messages) => messages,
|
||||||
|
Err(e) => vec![MessageResource::from(e)],
|
||||||
|
},
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
// Request couldn't be sent
|
||||||
|
Err(Error::Network(MessageResource::from(e)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// Same as function above but blocking
|
||||||
|
pub fn perform_request_without_client_sync<B: Serialize, R: DeserializeOwned>(
|
||||||
|
base_url: String,
|
||||||
|
method: reqwest::Method,
|
||||||
|
path: String,
|
||||||
|
body: Option<B>,
|
||||||
|
expected_status_code: u16,
|
||||||
|
headers: Vec<(String, String)>,
|
||||||
|
) -> Result<R, Error> {
|
||||||
|
let client = reqwest::blocking::Client::new();
|
||||||
|
let mut req_incomplete =
|
||||||
|
client.request(method, format!("{url}{path}", url = base_url, path = path));
|
||||||
|
|
||||||
|
for header in headers {
|
||||||
|
req_incomplete = req_incomplete.header(&header.0, &header.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
let req_complete = match body {
|
||||||
|
Some(b) => req_incomplete.json(&b),
|
||||||
|
None => req_incomplete.header("content-length", 0),
|
||||||
|
};
|
||||||
|
println!("{:?}", req_complete);
|
||||||
|
match req_complete.send() {
|
||||||
|
// Error handling here
|
||||||
|
Ok(res) => {
|
||||||
|
// Request sent correctly
|
||||||
|
match res.status().as_u16() == expected_status_code {
|
||||||
|
true => {
|
||||||
|
match res.json::<R>() {
|
||||||
|
Ok(resp_dto) => Ok(resp_dto), // Return correctly deserialized obj
|
||||||
|
Err(err) => Err(Error::Serde(MessageResource::from(err))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
false => {
|
||||||
|
//If status code is any other than expected
|
||||||
|
Err(Error::UnexpectedStatusCode(
|
||||||
|
expected_status_code,
|
||||||
|
res.status().as_u16(),
|
||||||
|
match res.json::<Vec<MessageResource>>() {
|
||||||
|
Ok(messages) => messages,
|
||||||
|
Err(e) => vec![MessageResource::from(e)],
|
||||||
|
},
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
// Request couldn't be sent
|
||||||
|
Err(Error::Network(MessageResource::from(e)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,5 +1,5 @@
|
||||||
use actix_web_utils::enums::error::Error;
|
|
||||||
use dev_dtos::{dtos::user::user_dtos::{UserForCreationDto, UserForLoginDto, UserForAuthenticationDto}, domain::user::{user::User, token::Token}};
|
use dev_dtos::{dtos::user::user_dtos::{UserForCreationDto, UserForLoginDto, UserForAuthenticationDto}, domain::user::{user::User, token::Token}};
|
||||||
|
use err::Error;
|
||||||
use reqwest::{Client, Method};
|
use reqwest::{Client, Method};
|
||||||
|
|
||||||
use crate::middleware::client::perform_request;
|
use crate::middleware::client::perform_request;
|
||||||
|
@ -14,8 +14,8 @@ pub async fn authenticate_user_with_token(client: &Client, user: &UserForAuthent
|
||||||
pub async fn create_user(client: &Client, user: &UserForCreationDto) -> Result<Token, Error> {
|
pub async fn create_user(client: &Client, user: &UserForCreationDto) -> Result<Token, Error> {
|
||||||
perform_request::<&UserForCreationDto, Token>(BASE_URL_USER_SVC.to_string(), client, Method::POST, "/user".to_string(), Some(user), 200, vec![]).await
|
perform_request::<&UserForCreationDto, Token>(BASE_URL_USER_SVC.to_string(), client, Method::POST, "/user".to_string(), Some(user), 200, vec![]).await
|
||||||
}
|
}
|
||||||
pub async fn authenticate_user_with_password(client: &Client, user: &UserForLoginDto) -> Result<User, Error> {
|
pub async fn authenticate_user_with_password(client: &Client, user: &UserForLoginDto) -> Result<Token, Error> {
|
||||||
perform_request::<&UserForLoginDto, User>(BASE_URL_USER_SVC.to_string(), client, Method::POST, "/user/auth/password".to_string(), Some(user), 200, vec![]).await
|
perform_request::<&UserForLoginDto, Token>(BASE_URL_USER_SVC.to_string(), client, Method::POST, "/user/auth/password".to_string(), Some(user), 200, vec![]).await
|
||||||
}
|
}
|
||||||
pub async fn refresh_token_for_user(client: &Client, user: &UserForAuthenticationDto, user_id: &i32) -> Result<Token, Error> {
|
pub async fn refresh_token_for_user(client: &Client, user: &UserForAuthenticationDto, user_id: &i32) -> Result<Token, Error> {
|
||||||
perform_request::<&UserForAuthenticationDto, Token>(BASE_URL_USER_SVC.to_string(), client, Method::PATCH, format!("/user/refresh/{}", user_id), None, 200, vec![(String::from("refresh-token"), user.token.clone())]).await
|
perform_request::<&UserForAuthenticationDto, Token>(BASE_URL_USER_SVC.to_string(), client, Method::PATCH, format!("/user/refresh/{}", user_id), None, 200, vec![(String::from("refresh-token"), user.token.clone())]).await
|
||||||
|
|
Loading…
Reference in New Issue