Skip to content

Commit

Permalink
Navigation complete
Browse files Browse the repository at this point in the history
Also improved error handling
  • Loading branch information
berinaniesh committed Nov 3, 2023
1 parent 944184a commit 578b7c7
Show file tree
Hide file tree
Showing 6 changed files with 156 additions and 65 deletions.
9 changes: 5 additions & 4 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ rand = "0.8.5"
serde = { version = "1.0.188", features = ["derive"] }
serde_json = { version = "1.0.106", features = ["preserve_order"] }
sqlx = { version = "0.7.1", features = ["runtime-tokio-rustls", "postgres", "macros"] }
thiserror = "1.0.50"
tokio = { version = "1.32.0", features = ["fs", "rt-multi-thread", "macros"] }
utoipa = { version = "4.0.0", features = ["actix_extras", "preserve_order"] }
utoipa-swagger-ui = { version = "4.0.0", features = ["actix-web"] }
55 changes: 55 additions & 0 deletions src/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
use thiserror::Error;
use actix_web::error::ResponseError;
use actix_web::http::StatusCode;
use actix_web::HttpResponse;
use sqlx::Error as SqlxError;
use serde_json::json;

#[derive(Debug, Error)]
pub enum AppError {
#[error("The requested resource was not found in the database")]
NotFound,

#[error("Internal Server Error")]
InternalServerError,
}

impl ResponseError for AppError {
fn error_response(&self) -> HttpResponse {
match self {
AppError::NotFound => {
HttpResponse::NotFound().json(json!({
"message": self.to_string()
}))
}
AppError::InternalServerError => {
HttpResponse::InternalServerError().json(json!({
"message": self.to_string()
}))
}
}
}
fn status_code(&self) -> StatusCode {
match *self {
AppError::NotFound => {
StatusCode::NOT_FOUND
}
AppError::InternalServerError => {
StatusCode::INTERNAL_SERVER_ERROR
}
}
}
}

impl From<SqlxError> for AppError {
fn from(err: SqlxError) -> Self {
match err {
SqlxError::RowNotFound | SqlxError::ColumnNotFound(_) => {
AppError::NotFound
}
_ => {
AppError::InternalServerError
}
}
}
}
3 changes: 2 additions & 1 deletion src/main.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
mod models;
mod routes;
mod error;

#[cfg(test)]
mod tests;
Expand Down Expand Up @@ -33,7 +34,7 @@ pub struct AppData {
search,
get_next_page,
),
components(schemas(Hello, TranslationInfo, Verse, Book, Count, SearchParameters, CurrentPage, NextPage))
components(schemas(Hello, TranslationInfo, Verse, Book, Count, SearchParameters, PageIn, PageOut))
)]
struct ApiDoc;

Expand Down
9 changes: 4 additions & 5 deletions src/models.rs
Original file line number Diff line number Diff line change
Expand Up @@ -154,16 +154,15 @@ pub struct SearchParameters {
}

#[derive(Debug, Deserialize, ToSchema)]
pub struct CurrentPage {
pub struct PageIn {
pub book: Option<String>,
pub abbreviation: Option<String>,
pub abbreviation: Option<String>,
pub chapter: i64,
}

#[derive(Debug, Serialize, ToSchema)]
pub struct NextPage {
pub struct PageOut {
pub book: String,
pub abbreviation: String,
pub chapter: i32,
pub bible_ended: bool,
pub chapter: i64,
}
144 changes: 89 additions & 55 deletions src/routes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use sqlx::{Postgres, QueryBuilder};

use crate::models::*;
use crate::AppData;
use crate::error::AppError;

#[allow(unused_assignments)]
pub async fn query_verses(qp: web::Query<VerseFilter>, app_data: web::Data<AppData>) -> Vec<Verse> {
Expand Down Expand Up @@ -303,16 +304,18 @@ pub async fn get_chaptercount(
pub async fn get_translation_info(
app_data: web::Data<AppData>,
path: web::Path<String>,
) -> HttpResponse {
) -> Result<HttpResponse, AppError> {
let translation = path.into_inner().to_uppercase();
let q = sqlx::query_as!(TranslationInfo, r#"SELECT name, l.lname as language, full_name, year, license, description from "Translation" join (select id, name as lname from "Language") l on l.id=language_id WHERE name=$1"#, &translation).fetch_one(&app_data.pool).await;
if q.is_err() {
return HttpResponse::BadRequest().json(json!(format!(
"The requested translation {} is not found on the server",
&translation
)));
}
return HttpResponse::Ok().json(q.unwrap());
let q = sqlx::query_as!(
TranslationInfo,
r#"
SELECT name, l.lname AS language,
full_name, year, license, description
FROM "Translation" JOIN
(SELECT id, name AS lname FROM "Language") l
ON l.id=language_id WHERE name=$1"#,
&translation).fetch_one(&app_data.pool).await?;
return Ok(HttpResponse::Ok().json(q));
}

/// Get a list of books with respect to the translation
Expand Down Expand Up @@ -383,65 +386,96 @@ pub async fn search(search_parameters: web::Json<SearchParameters>, app_data: we
return HttpResponse::Ok().json(verses);
}

/// Get the next chapter / book to go to
/// Get the previous and next chapter / book to go to
///
/// The frontend needs to know what page to go to once
/// a user finishes reading one chapter and since the frontend
/// doesn't have access to the database and it needs a few calls to
/// the API to figure it out, it'd be nice to have the API give
/// the information directly.
/// The frontend needs to know what page lies before and
/// after a specific chapter. So, instead of making multiple
/// API calls, the information is sent in a separate endpoint
#[utoipa::path(
post,
tag = "Frontend Helper",
path = "/next",
request_body = CurrentPage,
path = "/nav",
request_body = PageIn,
responses(
(status = 200, description = "Returns info about the next page to navigate to", body = NextPage),
(status = 200, description = "Returns info about the previous and next pages to navigate to", body = PageOut),
(status = 400, description = "Atleast one argument of book or abbreviation is required",),
),
)]
#[post("/next")]
pub async fn get_next_page(current_page: web::Json<CurrentPage>, app_data: web::Data<AppData>) -> HttpResponse {
#[post("/nav")]
pub async fn get_next_page(current_page: web::Json<PageIn>, app_data: web::Data<AppData>) -> Result<HttpResponse, AppError> {
if current_page.book.is_none() && current_page.abbreviation.is_none() {
return HttpResponse::BadRequest().json(json!({
return Ok(HttpResponse::BadRequest().json(json!({
"message": "Either one of book or abbreviation is required"
}))
}
let mut is_revelation = false;
if current_page.book.is_some() {
let book = current_page.book.clone().unwrap();
if book == "Revelation" {
is_revelation = true;
}
}
if current_page.abbreviation.is_some() {
})))
}
let previous: Option<PageOut>;
let next: Option<PageOut>;
let book_id;
if let Some(ref x) = current_page.book {
book_id = sqlx::query!(
r#"
SELECT id FROM "Book" WHERE name=$1
"#,
x).fetch_one(&app_data.pool).await?.id;
} else {
let abbreviation = current_page.abbreviation.clone().unwrap().to_uppercase();
if abbreviation == "REV" {
is_revelation = true;
}
}
if is_revelation && current_page.chapter == 22 {
let next_page = NextPage{book: "Genesis".to_string(), abbreviation: "GEN".to_string(), chapter: 1, bible_ended: true};
return HttpResponse::Ok().json(next_page);
book_id = sqlx::query!(
r#"
SELECT id FROM "Book" WHERE abbreviation=$1
"#,
abbreviation).fetch_one(&app_data.pool).await?.id;
}
let mut query_builder: QueryBuilder<Postgres> = QueryBuilder::new(
r#"SELECT COUNT(*) AS count FROM "Chapter" WHERE book_id=(SELECT id FROM "Book" WHERE"#,
);
if current_page.book.is_some() {
let book = current_page.book.clone().unwrap();
query_builder.push(" name=");
query_builder.push_bind(book);
query_builder.push(")");

if book_id == 1 && current_page.chapter == 1 {
previous = None;
} else {
let abbreviation = current_page.abbreviation.clone().unwrap();
query_builder.push(" abbreviation=");
query_builder.push_bind(abbreviation);
query_builder.push(")");
if current_page.chapter == 1 {
let previous_chapter_count = sqlx::query!(
r#"
SELECT COUNT(*) AS count FROM "Chapter" WHERE book_id=$1
"#,
book_id-1).fetch_one(&app_data.pool).await?.count.unwrap();
let previous_book = sqlx::query!(
r#"
SELECT name, abbreviation FROM "Book" WHERE id=$1
"#,
book_id-1).fetch_one(&app_data.pool).await?;
previous = Some(PageOut {book: previous_book.name, abbreviation: previous_book.abbreviation, chapter: previous_chapter_count});
} else {
let prev = sqlx::query!(
r#"
SELECT name, abbreviation FROM "Book" WHERE id=$1
"#,
book_id).fetch_one(&app_data.pool).await?;
previous = Some(PageOut{book: prev.name, abbreviation: prev.abbreviation, chapter: current_page.chapter - 1});
}
}
let query = query_builder.build_query_as::<Count>();
let current_book_chapter_count = query.fetch_one(&app_data.pool).await.unwrap().count;
if current_page.chapter < current_book_chapter_count {
// let next_page = NextPage {}

if book_id == 66 && current_page.chapter == 22 {
next = None;
} else {
let current_book_length = sqlx::query!(
r#"
SELECT COUNT(*) FROM "Chapter" WHERE book_id=$1
"#,
book_id).fetch_one(&app_data.pool).await?.count.unwrap();
if current_page.chapter == current_book_length {
let next_book = sqlx::query!(
r#"
SELECT name, abbreviation FROM "Book" WHERE id=$1
"#,
book_id+1).fetch_one(&app_data.pool).await?;
next = Some(PageOut{book: next_book.name, abbreviation: next_book.abbreviation, chapter: 1})
} else {
let bo = sqlx::query!(
r#"
SELECT name, abbreviation FROM "Book" WHERE id=$1
"#,
book_id).fetch_one(&app_data.pool).await?;
next = Some(PageOut{book: bo.name, abbreviation: bo.abbreviation, chapter: current_page.chapter + 1});
}
}
return HttpResponse::Ok().json("hello");

return Ok(HttpResponse::Ok().json(json!({"previous": previous, "next":next})));

}

0 comments on commit 578b7c7

Please sign in to comment.