Add endpoints for search and pagination
also add non-blocking logger
This commit is contained in:
parent
0c64225bcf
commit
c993ef87ee
33
Cargo.lock
generated
33
Cargo.lock
generated
@ -74,6 +74,7 @@ dependencies = [
|
|||||||
"timeago",
|
"timeago",
|
||||||
"tracing",
|
"tracing",
|
||||||
"tracing-actix-web",
|
"tracing-actix-web",
|
||||||
|
"tracing-appender",
|
||||||
"tracing-bunyan-formatter",
|
"tracing-bunyan-formatter",
|
||||||
"tracing-futures",
|
"tracing-futures",
|
||||||
"tracing-log",
|
"tracing-log",
|
||||||
@ -837,6 +838,27 @@ dependencies = [
|
|||||||
"cfg-if 1.0.0",
|
"cfg-if 1.0.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "crossbeam-channel"
|
||||||
|
version = "0.5.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "06ed27e177f16d65f0f0c22a213e17c696ace5dd64b14258b52f9417ccb52db4"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if 1.0.0",
|
||||||
|
"crossbeam-utils",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "crossbeam-utils"
|
||||||
|
version = "0.8.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4feb231f0d4d6af81aed15928e58ecf5816aa62a2393e2c82f46973e92a9a278"
|
||||||
|
dependencies = [
|
||||||
|
"autocfg 1.0.1",
|
||||||
|
"cfg-if 1.0.0",
|
||||||
|
"lazy_static",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "crypto-mac"
|
name = "crypto-mac"
|
||||||
version = "0.10.0"
|
version = "0.10.0"
|
||||||
@ -2861,6 +2883,17 @@ dependencies = [
|
|||||||
"uuid",
|
"uuid",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tracing-appender"
|
||||||
|
version = "0.1.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9965507e507f12c8901432a33e31131222abac31edd90cabbcf85cf544b7127a"
|
||||||
|
dependencies = [
|
||||||
|
"chrono",
|
||||||
|
"crossbeam-channel",
|
||||||
|
"tracing-subscriber",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tracing-attributes"
|
name = "tracing-attributes"
|
||||||
version = "0.1.15"
|
version = "0.1.15"
|
||||||
|
@ -42,6 +42,7 @@ diesel-tracing = { version = "0.1.4", features = ["sqlite"] }
|
|||||||
validators = "0.22.5"
|
validators = "0.22.5"
|
||||||
diesel-derive-newtype = "0.1"
|
diesel-derive-newtype = "0.1"
|
||||||
derive_more = "0.99.13"
|
derive_more = "0.99.13"
|
||||||
|
tracing-appender = "0.1.2"
|
||||||
|
|
||||||
[dependencies.validators-derive]
|
[dependencies.validators-derive]
|
||||||
version = "0.22.5"
|
version = "0.22.5"
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
use diesel::prelude::*;
|
use diesel::prelude::*;
|
||||||
|
|
||||||
use crate::models;
|
use crate::models::{self, Pagination, UserId};
|
||||||
use crate::{errors, models::Password};
|
use crate::{errors, models::Password};
|
||||||
use bcrypt::{hash, verify, DEFAULT_COST};
|
use bcrypt::{hash, verify, DEFAULT_COST};
|
||||||
use validators::prelude::*;
|
use validators::prelude::*;
|
||||||
|
|
||||||
pub fn find_user_by_uid(
|
pub fn find_user_by_uid(
|
||||||
uid: i32,
|
uid: &UserId,
|
||||||
conn: &impl diesel::Connection<Backend = diesel::sqlite::Sqlite>,
|
conn: &impl diesel::Connection<Backend = diesel::sqlite::Sqlite>,
|
||||||
) -> Result<Option<models::User>, errors::DomainError> {
|
) -> Result<Option<models::User>, errors::DomainError> {
|
||||||
use crate::schema::users::dsl::*;
|
use crate::schema::users::dsl::*;
|
||||||
@ -42,6 +42,46 @@ pub fn get_all(
|
|||||||
.load::<models::User>(conn)?)
|
.load::<models::User>(conn)?)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// def findAll(userId: Long, limit: Int, offset: Int) = db.run {
|
||||||
|
// for {
|
||||||
|
// comments <- query.filter(_.creatorId === userId)
|
||||||
|
// .sortBy(_.createdAt)
|
||||||
|
// .drop(offset).take(limit)
|
||||||
|
// .result
|
||||||
|
// numberOfComments <- query.filter(_.creatorId === userId).length.result
|
||||||
|
// } yield PaginatedResult(
|
||||||
|
// totalCount = numberOfComments,
|
||||||
|
// entities = comments.toList,
|
||||||
|
// hasNextPage = numberOfComments - (offset + limit) > 0
|
||||||
|
// )
|
||||||
|
// }
|
||||||
|
|
||||||
|
pub fn get_users_paginated(
|
||||||
|
// user_id: UserId,
|
||||||
|
pagination: &Pagination,
|
||||||
|
conn: &impl diesel::Connection<Backend = diesel::sqlite::Sqlite>,
|
||||||
|
) -> Result<Vec<models::User>, errors::DomainError> {
|
||||||
|
// use crate::schema::users::dsl::*;
|
||||||
|
Ok(query::_paginate_result(&pagination).load::<models::User>(conn)?)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn search_users(
|
||||||
|
query: &str,
|
||||||
|
pagination: &Pagination,
|
||||||
|
conn: &impl diesel::Connection<Backend = diesel::sqlite::Sqlite>,
|
||||||
|
) -> Result<Vec<models::User>, errors::DomainError> {
|
||||||
|
use crate::schema::users::dsl::*;
|
||||||
|
// Ok(users
|
||||||
|
// .filter(name.like(format!("%{}%", query)))
|
||||||
|
// .order_by(created_at)
|
||||||
|
// .offset(pagination.calc_offset().as_uint().into())
|
||||||
|
// .limit(pagination.limit.as_uint().into())
|
||||||
|
// .load::<models::User>(conn)?)
|
||||||
|
Ok(query::_paginate_result(&pagination)
|
||||||
|
.filter(name.like(format!("%{}%", query)))
|
||||||
|
.load::<models::User>(conn)?)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn insert_new_user(
|
pub fn insert_new_user(
|
||||||
nu: models::NewUser,
|
nu: models::NewUser,
|
||||||
conn: &impl diesel::Connection<Backend = diesel::sqlite::Sqlite>,
|
conn: &impl diesel::Connection<Backend = diesel::sqlite::Sqlite>,
|
||||||
@ -81,7 +121,7 @@ pub fn verify_password(
|
|||||||
}
|
}
|
||||||
|
|
||||||
mod query {
|
mod query {
|
||||||
use diesel::prelude::*;
|
use super::*;
|
||||||
use diesel::sql_types::Integer;
|
use diesel::sql_types::Integer;
|
||||||
use diesel::sql_types::Text;
|
use diesel::sql_types::Text;
|
||||||
use diesel::sql_types::Timestamp;
|
use diesel::sql_types::Timestamp;
|
||||||
@ -91,11 +131,19 @@ mod query {
|
|||||||
type Query<'a, B, T> = crate::schema::users::BoxedQuery<'a, B, T>;
|
type Query<'a, B, T> = crate::schema::users::BoxedQuery<'a, B, T>;
|
||||||
|
|
||||||
pub fn _get_user_by_name(
|
pub fn _get_user_by_name(
|
||||||
|
) -> Query<'static, Sqlite, (Integer, Text, Text, Timestamp)> {
|
||||||
|
use crate::schema::users::dsl::*;
|
||||||
|
users.into_boxed()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn _paginate_result(
|
||||||
|
pagination: &Pagination,
|
||||||
) -> Query<'static, Sqlite, (Integer, Text, Text, Timestamp)> {
|
) -> Query<'static, Sqlite, (Integer, Text, Text, Timestamp)> {
|
||||||
use crate::schema::users::dsl::*;
|
use crate::schema::users::dsl::*;
|
||||||
users
|
users
|
||||||
.select(users::all_columns())
|
.order_by(created_at)
|
||||||
// .filter(name.eq(user_name))
|
.offset(pagination.calc_offset().as_uint().into())
|
||||||
|
.limit(pagination.limit.as_uint().into())
|
||||||
.into_boxed()
|
.into_boxed()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -42,12 +42,12 @@ impl ResponseError for DomainError {
|
|||||||
.json(ApiResponse::failure(err.to_string()))
|
.json(ApiResponse::failure(err.to_string()))
|
||||||
}
|
}
|
||||||
DomainError::DbError { source: _ } => {
|
DomainError::DbError { source: _ } => {
|
||||||
tracing::error!("{}", err);
|
let _ = tracing::error!("{}", err);
|
||||||
HttpResponse::InternalServerError()
|
HttpResponse::InternalServerError()
|
||||||
.json(ApiResponse::failure("Error in database".to_owned()))
|
.json(ApiResponse::failure("Error in database".to_owned()))
|
||||||
}
|
}
|
||||||
DomainError::DbPoolError { source: _ } => {
|
DomainError::DbPoolError { source: _ } => {
|
||||||
tracing::error!("{}", err);
|
let _ = tracing::error!("{}", err);
|
||||||
HttpResponse::InternalServerError().json(ApiResponse::failure(
|
HttpResponse::InternalServerError().json(ApiResponse::failure(
|
||||||
"Error getting database pool".to_owned(),
|
"Error getting database pool".to_owned(),
|
||||||
))
|
))
|
||||||
@ -61,7 +61,7 @@ impl ResponseError for DomainError {
|
|||||||
.json(ApiResponse::failure(err.to_string()))
|
.json(ApiResponse::failure(err.to_string()))
|
||||||
}
|
}
|
||||||
DomainError::ThreadPoolError { message: _ } => {
|
DomainError::ThreadPoolError { message: _ } => {
|
||||||
tracing::error!("{}", err);
|
let _ = tracing::error!("{}", err);
|
||||||
HttpResponse::InternalServerError().json(ApiResponse::failure(
|
HttpResponse::InternalServerError().json(ApiResponse::failure(
|
||||||
"Thread pool error occurred".to_owned(),
|
"Thread pool error occurred".to_owned(),
|
||||||
))
|
))
|
||||||
@ -69,7 +69,7 @@ impl ResponseError for DomainError {
|
|||||||
DomainError::AuthError { message: _ } => HttpResponse::Forbidden()
|
DomainError::AuthError { message: _ } => HttpResponse::Forbidden()
|
||||||
.json(ApiResponse::failure(err.to_string())),
|
.json(ApiResponse::failure(err.to_string())),
|
||||||
DomainError::FieldValidationError { message: _ } => {
|
DomainError::FieldValidationError { message: _ } => {
|
||||||
tracing::error!("{}", err);
|
let _ = tracing::error!("{}", err);
|
||||||
HttpResponse::BadRequest()
|
HttpResponse::BadRequest()
|
||||||
.json(ApiResponse::failure(err.to_string()))
|
.json(ApiResponse::failure(err.to_string()))
|
||||||
}
|
}
|
||||||
|
18
src/lib.rs
18
src/lib.rs
@ -73,11 +73,19 @@ pub fn configure_app(app_data: AppData) -> Box<dyn Fn(&mut ServiceConfig)> {
|
|||||||
"",
|
"",
|
||||||
web::get().to(routes::users::get_all_users),
|
web::get().to(routes::users::get_all_users),
|
||||||
)
|
)
|
||||||
|
.route(
|
||||||
|
"/search",
|
||||||
|
web::get().to(routes::users::search_users),
|
||||||
|
)
|
||||||
|
.route("", web::post().to(routes::users::add_user))
|
||||||
.route(
|
.route(
|
||||||
"/{user_id}",
|
"/{user_id}",
|
||||||
web::get().to(routes::users::get_user),
|
web::get().to(routes::users::get_user),
|
||||||
)
|
),
|
||||||
.route("", web::post().to(routes::users::add_user)),
|
)
|
||||||
|
.route(
|
||||||
|
"/pagination",
|
||||||
|
web::get().to(routes::users::get_users_paginated),
|
||||||
)
|
)
|
||||||
.route(
|
.route(
|
||||||
"/build-info",
|
"/build-info",
|
||||||
@ -108,7 +116,11 @@ pub fn id_service(
|
|||||||
|
|
||||||
pub async fn run(addr: String, app_data: AppData) -> io::Result<()> {
|
pub async fn run(addr: String, app_data: AppData) -> io::Result<()> {
|
||||||
let bi = get_build_info();
|
let bi = get_build_info();
|
||||||
tracing::info!("Starting {} {}", bi.crate_info.name, bi.crate_info.version);
|
let _ = tracing::info!(
|
||||||
|
"Starting {} {}",
|
||||||
|
bi.crate_info.name,
|
||||||
|
bi.crate_info.version
|
||||||
|
);
|
||||||
println!(
|
println!(
|
||||||
r#"
|
r#"
|
||||||
__ .__ .___
|
__ .__ .___
|
||||||
|
@ -78,6 +78,9 @@ pub fn setup_logger(format: LoggerFormat) -> io::Result<()> {
|
|||||||
)
|
)
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
|
let (non_blocking, _guard) =
|
||||||
|
tracing_appender::non_blocking(std::io::stdout());
|
||||||
|
|
||||||
let _ = LogTracer::init().map_err(|err| {
|
let _ = LogTracer::init().map_err(|err| {
|
||||||
io::Error::new(
|
io::Error::new(
|
||||||
ErrorKind::Other,
|
ErrorKind::Other,
|
||||||
@ -91,8 +94,8 @@ pub fn setup_logger(format: LoggerFormat) -> io::Result<()> {
|
|||||||
LoggerFormat::Json => {
|
LoggerFormat::Json => {
|
||||||
let formatting_layer = BunyanFormattingLayer::new(
|
let formatting_layer = BunyanFormattingLayer::new(
|
||||||
format!("actix-demo-{}", bi.crate_info.version),
|
format!("actix-demo-{}", bi.crate_info.version),
|
||||||
// Output the formatted spans to stdout.
|
// Output the formatted spans to non-blocking writer
|
||||||
std::io::stdout,
|
non_blocking,
|
||||||
);
|
);
|
||||||
let subscriber = Registry::default()
|
let subscriber = Registry::default()
|
||||||
.with(env_filter)
|
.with(env_filter)
|
||||||
|
@ -1,3 +1 @@
|
|||||||
pub mod csrf;
|
|
||||||
|
|
||||||
pub use self::csrf::*;
|
|
||||||
|
@ -7,6 +7,9 @@ use std::convert::TryFrom;
|
|||||||
use std::{convert::TryInto, str::FromStr};
|
use std::{convert::TryInto, str::FromStr};
|
||||||
use validators::prelude::*;
|
use validators::prelude::*;
|
||||||
|
|
||||||
|
///newtype to constrain id to positive int values
|
||||||
|
///
|
||||||
|
///sqlite does not allow u32 for primary keys
|
||||||
#[derive(
|
#[derive(
|
||||||
Debug,
|
Debug,
|
||||||
Clone,
|
Clone,
|
||||||
@ -34,14 +37,13 @@ impl FromStr for UserId {
|
|||||||
|
|
||||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
if let Ok(num) = s.parse::<u32>() {
|
if let Ok(num) = s.parse::<u32>() {
|
||||||
(num as u32)
|
num.try_into()
|
||||||
.try_into()
|
|
||||||
.map_err(|err| {
|
.map_err(|err| {
|
||||||
format!("error while converting user_id: {}", err)
|
format!("negative values are not allowed: {}", err)
|
||||||
})
|
})
|
||||||
.map(UserId)
|
.map(UserId)
|
||||||
} else {
|
} else {
|
||||||
Err("negative values are not allowed".to_owned())
|
Err("expected unsigned int, received string".to_owned())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -91,6 +93,94 @@ pub struct NewUser {
|
|||||||
pub password: Password,
|
pub password: Password,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Deserialize)]
|
||||||
|
#[serde(try_from = "u16")]
|
||||||
|
pub struct PaginationOffset(u16);
|
||||||
|
impl PaginationOffset {
|
||||||
|
pub fn as_uint(&self) -> u16 {
|
||||||
|
self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<u16> for PaginationOffset {
|
||||||
|
type Error = String;
|
||||||
|
fn try_from(value: u16) -> Result<Self, Self::Error> {
|
||||||
|
if value <= 2500 {
|
||||||
|
Ok(PaginationOffset(value))
|
||||||
|
} else {
|
||||||
|
Err("Failed to validate".to_owned())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Deserialize)]
|
||||||
|
#[serde(try_from = "u16")]
|
||||||
|
pub struct PaginationLimit(u16);
|
||||||
|
impl PaginationLimit {
|
||||||
|
pub fn as_uint(&self) -> u16 {
|
||||||
|
self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<u16> for PaginationLimit {
|
||||||
|
type Error = String;
|
||||||
|
fn try_from(value: u16) -> Result<Self, Self::Error> {
|
||||||
|
if value <= 50 {
|
||||||
|
Ok(PaginationLimit(value))
|
||||||
|
} else {
|
||||||
|
Err("Failed to validate".to_owned())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Deserialize)]
|
||||||
|
#[serde(try_from = "u16")]
|
||||||
|
pub struct PaginationPage(u16);
|
||||||
|
impl PaginationPage {
|
||||||
|
pub fn as_uint(&self) -> u16 {
|
||||||
|
self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<u16> for PaginationPage {
|
||||||
|
type Error = String;
|
||||||
|
fn try_from(value: u16) -> Result<Self, Self::Error> {
|
||||||
|
if value <= 50 {
|
||||||
|
Ok(PaginationPage(value))
|
||||||
|
} else {
|
||||||
|
Err("Failed to validate".to_owned())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Deserialize)]
|
||||||
|
pub struct Pagination {
|
||||||
|
pub page: PaginationPage,
|
||||||
|
pub limit: PaginationLimit,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Pagination {
|
||||||
|
pub fn calc_offset(&self) -> PaginationOffset {
|
||||||
|
let res = self.page.as_uint() * self.limit.as_uint();
|
||||||
|
PaginationOffset::try_from(res).unwrap()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Deserialize)]
|
||||||
|
pub struct SearchQuery(String);
|
||||||
|
|
||||||
|
impl SearchQuery {
|
||||||
|
pub fn as_str(&self) -> &str {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Deserialize)]
|
||||||
|
pub struct UserSearchRequest {
|
||||||
|
pub q: SearchQuery,
|
||||||
|
// pub pagination: Pagination
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use super::*;
|
use super::*;
|
||||||
@ -127,4 +217,18 @@ mod test {
|
|||||||
);
|
);
|
||||||
assert_eq!(mb_user.is_ok(), false);
|
assert_eq!(mb_user.is_ok(), false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn pagination_refinement_test() {
|
||||||
|
let mb_pag =
|
||||||
|
serde_json::from_str::<Pagination>(r#"{"limit":5,"page":5}"#);
|
||||||
|
// println!("{:?}", mb_pag);
|
||||||
|
assert_eq!(mb_pag.is_ok(), true);
|
||||||
|
let mb_pag =
|
||||||
|
serde_json::from_str::<Pagination>(r#"{"limit":51,"page":5}"#);
|
||||||
|
assert_eq!(mb_pag.is_ok(), false);
|
||||||
|
let mb_pag =
|
||||||
|
serde_json::from_str::<Pagination>(r#"{"limit":5,"page":51}"#);
|
||||||
|
assert_eq!(mb_pag.is_ok(), false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -46,7 +46,7 @@ pub async fn logout(
|
|||||||
) -> Result<HttpResponse, Error> {
|
) -> Result<HttpResponse, Error> {
|
||||||
let maybe_identity = id.identity();
|
let maybe_identity = id.identity();
|
||||||
let response = if let Some(identity) = maybe_identity {
|
let response = if let Some(identity) = maybe_identity {
|
||||||
tracing::info!("Logging out {user}", user = identity);
|
let _ = tracing::info!("Logging out {user}", user = identity);
|
||||||
id.forget();
|
id.forget();
|
||||||
HttpResponse::Found().header("location", "/").finish()
|
HttpResponse::Found().header("location", "/").finish()
|
||||||
} else {
|
} else {
|
||||||
|
@ -2,7 +2,7 @@ use actix_web::{web, HttpResponse};
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
actions,
|
actions,
|
||||||
models::{self, ApiResponse},
|
models::{self, ApiResponse, Pagination, UserId, UserSearchRequest},
|
||||||
};
|
};
|
||||||
use crate::{errors::DomainError, AppData};
|
use crate::{errors::DomainError, AppData};
|
||||||
use actix_web::error::ResponseError;
|
use actix_web::error::ResponseError;
|
||||||
@ -17,19 +17,20 @@ use actix_web::error::ResponseError;
|
|||||||
)]
|
)]
|
||||||
pub async fn get_user(
|
pub async fn get_user(
|
||||||
app_data: web::Data<AppData>,
|
app_data: web::Data<AppData>,
|
||||||
user_id: web::Path<i32>,
|
user_id: web::Path<UserId>,
|
||||||
) -> Result<HttpResponse, DomainError> {
|
) -> Result<HttpResponse, DomainError> {
|
||||||
let u_id = user_id.into_inner();
|
let u_id = user_id.into_inner();
|
||||||
tracing::info!("Getting user with id {}", u_id);
|
let u_id2 = u_id.clone();
|
||||||
|
let _ = tracing::info!("Getting user with id {}", u_id);
|
||||||
// use web::block to offload blocking Diesel code without blocking server thread
|
// use web::block to offload blocking Diesel code without blocking server thread
|
||||||
let res = web::block(move || {
|
let res = web::block(move || {
|
||||||
let pool = &app_data.pool;
|
let pool = &app_data.pool;
|
||||||
let conn = pool.get()?;
|
let conn = pool.get()?;
|
||||||
actions::find_user_by_uid(u_id, &conn)
|
actions::find_user_by_uid(&u_id2, &conn)
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
.map_err(|err| DomainError::new_thread_pool_error(err.to_string()))?;
|
.map_err(|err| DomainError::new_thread_pool_error(err.to_string()))?;
|
||||||
tracing::trace!("{:?}", res);
|
let _ = tracing::trace!("{:?}", res);
|
||||||
if let Some(user) = res {
|
if let Some(user) = res {
|
||||||
Ok(HttpResponse::Ok().json(ApiResponse::successful(user)))
|
Ok(HttpResponse::Ok().json(ApiResponse::successful(user)))
|
||||||
} else {
|
} else {
|
||||||
@ -73,7 +74,7 @@ pub async fn get_all_users(
|
|||||||
.await
|
.await
|
||||||
.map_err(|err| DomainError::new_thread_pool_error(err.to_string()))?;
|
.map_err(|err| DomainError::new_thread_pool_error(err.to_string()))?;
|
||||||
|
|
||||||
tracing::trace!("{:?}", users);
|
let _ = tracing::trace!("{:?}", users);
|
||||||
|
|
||||||
if !users.is_empty() {
|
if !users.is_empty() {
|
||||||
Ok(HttpResponse::Ok().json(ApiResponse::successful(users)))
|
Ok(HttpResponse::Ok().json(ApiResponse::successful(users)))
|
||||||
@ -84,6 +85,47 @@ pub async fn get_all_users(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument(level = "debug", skip(app_data))]
|
||||||
|
pub async fn get_users_paginated(
|
||||||
|
app_data: web::Data<AppData>,
|
||||||
|
pagination: web::Query<Pagination>,
|
||||||
|
) -> Result<HttpResponse, DomainError> {
|
||||||
|
let _ = tracing::info!("Paginated users request");
|
||||||
|
let users = web::block(move || {
|
||||||
|
let pool = &app_data.pool;
|
||||||
|
let conn = pool.get()?;
|
||||||
|
let p: Pagination = pagination.into_inner();
|
||||||
|
actions::get_users_paginated(&p, &conn)
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.map_err(|err| DomainError::new_thread_pool_error(err.to_string()))?;
|
||||||
|
|
||||||
|
let _ = tracing::trace!("{:?}", users);
|
||||||
|
|
||||||
|
Ok(HttpResponse::Ok().json(ApiResponse::successful(users)))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument(level = "debug", skip(app_data))]
|
||||||
|
pub async fn search_users(
|
||||||
|
app_data: web::Data<AppData>,
|
||||||
|
query: web::Query<UserSearchRequest>,
|
||||||
|
pagination: web::Query<Pagination>,
|
||||||
|
) -> Result<HttpResponse, DomainError> {
|
||||||
|
let _ = tracing::info!("Search users request");
|
||||||
|
let users = web::block(move || {
|
||||||
|
let pool = &app_data.pool;
|
||||||
|
let conn = pool.get()?;
|
||||||
|
let p: Pagination = pagination.into_inner();
|
||||||
|
actions::search_users(query.q.as_str(), &p, &conn)
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.map_err(|err| DomainError::new_thread_pool_error(err.to_string()))?;
|
||||||
|
|
||||||
|
let _ = tracing::trace!("{:?}", users);
|
||||||
|
|
||||||
|
Ok(HttpResponse::Ok().json(ApiResponse::successful(users)))
|
||||||
|
}
|
||||||
|
|
||||||
/// Inserts new user with name defined in form.
|
/// Inserts new user with name defined in form.
|
||||||
#[tracing::instrument(level = "debug", skip(app_data))]
|
#[tracing::instrument(level = "debug", skip(app_data))]
|
||||||
pub async fn add_user(
|
pub async fn add_user(
|
||||||
@ -98,7 +140,7 @@ pub async fn add_user(
|
|||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
.map(|user| {
|
.map(|user| {
|
||||||
tracing::trace!("{:?}", user);
|
let _ = tracing::trace!("{:?}", user);
|
||||||
HttpResponse::Ok().json(ApiResponse::successful(user))
|
HttpResponse::Created().json(ApiResponse::successful(user))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -15,7 +15,7 @@ mod tests {
|
|||||||
let resp = common::test_app().await.unwrap().call(req).await.unwrap();
|
let resp = common::test_app().await.unwrap().call(req).await.unwrap();
|
||||||
assert_eq!(resp.status(), StatusCode::OK);
|
assert_eq!(resp.status(), StatusCode::OK);
|
||||||
let body: build_info::BuildInfo = test::read_body_json(resp).await;
|
let body: build_info::BuildInfo = test::read_body_json(resp).await;
|
||||||
tracing::debug!("{:?}", body);
|
let _ = tracing::debug!("{:?}", body);
|
||||||
assert_eq!(body, *get_build_info());
|
assert_eq!(body, *get_build_info());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,34 +8,40 @@ mod tests {
|
|||||||
use actix_web::http::StatusCode;
|
use actix_web::http::StatusCode;
|
||||||
use actix_web::test;
|
use actix_web::test;
|
||||||
|
|
||||||
#[actix_rt::test]
|
mod get_users_api {
|
||||||
async fn get_users_api_should_return_error_message_if_no_users_exist() {
|
use super::*;
|
||||||
let req = test::TestRequest::get().uri("/api/users").to_request();
|
|
||||||
let resp = common::test_app().await.unwrap().call(req).await.unwrap();
|
|
||||||
assert_eq!(resp.status(), StatusCode::NOT_FOUND);
|
|
||||||
let body: ApiResponse<String> = test::read_body_json(resp).await;
|
|
||||||
tracing::debug!("{:?}", body);
|
|
||||||
assert_eq!(
|
|
||||||
body,
|
|
||||||
ApiResponse::failure(
|
|
||||||
"Entity does not exist - No users available".to_owned()
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
async fn get_user_api_should_return_error_message_if_user_with_id_does_not_exist(
|
async fn should_return_error_message_if_no_users_exist() {
|
||||||
) {
|
let req = test::TestRequest::get().uri("/api/users").to_request();
|
||||||
let req = test::TestRequest::get().uri("/api/users/1").to_request();
|
let resp =
|
||||||
let resp = common::test_app().await.unwrap().call(req).await.unwrap();
|
common::test_app().await.unwrap().call(req).await.unwrap();
|
||||||
assert_eq!(resp.status(), StatusCode::NOT_FOUND);
|
assert_eq!(resp.status(), StatusCode::NOT_FOUND);
|
||||||
let body: ApiResponse<String> = test::read_body_json(resp).await;
|
let body: ApiResponse<String> = test::read_body_json(resp).await;
|
||||||
tracing::debug!("{:?}", body);
|
let _ = tracing::debug!("{:?}", body);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
body,
|
body,
|
||||||
ApiResponse::failure(
|
ApiResponse::failure(
|
||||||
"Entity does not exist - No user found with uid: 1".to_owned()
|
"Entity does not exist - No users available".to_owned()
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[actix_rt::test]
|
||||||
|
async fn should_return_error_message_if_user_with_id_does_not_exist() {
|
||||||
|
let req = test::TestRequest::get().uri("/api/users/1").to_request();
|
||||||
|
let resp =
|
||||||
|
common::test_app().await.unwrap().call(req).await.unwrap();
|
||||||
|
assert_eq!(resp.status(), StatusCode::NOT_FOUND);
|
||||||
|
let body: ApiResponse<String> = test::read_body_json(resp).await;
|
||||||
|
let _ = tracing::debug!("{:?}", body);
|
||||||
|
assert_eq!(
|
||||||
|
body,
|
||||||
|
ApiResponse::failure(
|
||||||
|
"Entity does not exist - No user found with uid: 1"
|
||||||
|
.to_owned()
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user