From 2ef85042af9c5eaa40d03c8b62409b68360aebab Mon Sep 17 00:00:00 2001 From: "husni.zuhdi@accelbyte.net" Date: Thu, 19 Sep 2024 00:41:32 +0700 Subject: [PATCH 1/7] chore(build): remove postgresql docker compose container --- build/docker-compose.yml | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/build/docker-compose.yml b/build/docker-compose.yml index d80876c..813d264 100644 --- a/build/docker-compose.yml +++ b/build/docker-compose.yml @@ -8,15 +8,3 @@ services: - "../.env" ports: - ${SVC_PORT}:${SVC_PORT} - db: - image: postgres:12-alpine - restart: always - env_file: - - "../.env" - ports: - - 5432:5432 - volumes: - - db-data:/var/lib/postgresql/data - -volumes: - db-data: {} From 427c416fe04a6f3cb3c0e057f095ff092c40a195 Mon Sep 17 00:00:00 2001 From: "husni.zuhdi@accelbyte.net" Date: Thu, 19 Sep 2024 00:42:19 +0700 Subject: [PATCH 2/7] feat: update all port, repo, and usecase method to return option --- internal/src/port/api/query.rs | 4 ++-- internal/src/port/blog/command.rs | 6 +++--- internal/src/port/blog/query.rs | 6 +++--- internal/src/repo/api.rs | 4 ++-- internal/src/repo/blog.rs | 13 ++++++------- internal/src/usecase/api.rs | 4 ++-- internal/src/usecase/blog.rs | 12 ++++++------ 7 files changed, 24 insertions(+), 25 deletions(-) diff --git a/internal/src/port/api/query.rs b/internal/src/port/api/query.rs index af753a4..e6f25b7 100644 --- a/internal/src/port/api/query.rs +++ b/internal/src/port/api/query.rs @@ -3,6 +3,6 @@ use async_trait::async_trait; #[async_trait] pub trait ApiQueryPort { - async fn list_metadata(&self) -> Vec; - async fn fetch(&self, metadata: BlogMetadata) -> Blog; + async fn list_metadata(&self) -> Option>; + async fn fetch(&self, metadata: BlogMetadata) -> Option; } diff --git a/internal/src/port/blog/command.rs b/internal/src/port/blog/command.rs index a1c75d6..9716a80 100644 --- a/internal/src/port/blog/command.rs +++ b/internal/src/port/blog/command.rs @@ -13,7 +13,7 @@ pub trait BlogCommandPort { filename: BlogFilename, source: BlogSource, body: BlogBody, - ) -> Blog; + ) -> Option; async fn update( &mut self, id: BlogId, @@ -21,6 +21,6 @@ pub trait BlogCommandPort { filename: Option, source: Option, body: Option, - ) -> Blog; - async fn delete(&mut self, id: BlogId) -> BlogDeleted; + ) -> Option; + async fn delete(&mut self, id: BlogId) -> Option; } diff --git a/internal/src/port/blog/query.rs b/internal/src/port/blog/query.rs index 045535d..f598f36 100644 --- a/internal/src/port/blog/query.rs +++ b/internal/src/port/blog/query.rs @@ -3,7 +3,7 @@ use async_trait::async_trait; #[async_trait] pub trait BlogQueryPort { - async fn find(&self, id: BlogId) -> Blog; - async fn find_blogs(&self, start: BlogStartPage, end: BlogEndPage) -> Vec; - async fn check_id(&self, id: BlogId) -> BlogStored; + async fn find(&self, id: BlogId) -> Option; + async fn find_blogs(&self, start: BlogStartPage, end: BlogEndPage) -> Option>; + async fn check_id(&self, id: BlogId) -> Option; } diff --git a/internal/src/repo/api.rs b/internal/src/repo/api.rs index e238dc2..49fb5de 100644 --- a/internal/src/repo/api.rs +++ b/internal/src/repo/api.rs @@ -6,6 +6,6 @@ clone_trait_object!(ApiRepo); #[async_trait] pub trait ApiRepo: DynClone { - async fn list_metadata(&self) -> Vec; - async fn fetch(&self, metadata: BlogMetadata) -> Blog; + async fn list_metadata(&self) -> Option>; + async fn fetch(&self, metadata: BlogMetadata) -> Option; } diff --git a/internal/src/repo/blog.rs b/internal/src/repo/blog.rs index aacee75..12303e4 100644 --- a/internal/src/repo/blog.rs +++ b/internal/src/repo/blog.rs @@ -9,9 +9,9 @@ clone_trait_object!(BlogRepo); #[async_trait] pub trait BlogRepo: DynClone { - async fn find(&self, id: BlogId) -> Blog; - async fn find_blogs(&self, start: BlogStartPage, end: BlogEndPage) -> Vec; - async fn check_id(&self, id: BlogId) -> BlogStored; + async fn find(&self, id: BlogId) -> Option; + async fn find_blogs(&self, start: BlogStartPage, end: BlogEndPage) -> Option>; + async fn check_id(&self, id: BlogId) -> Option; async fn add( &mut self, id: BlogId, @@ -19,8 +19,7 @@ pub trait BlogRepo: DynClone { filename: BlogFilename, source: BlogSource, body: BlogBody, - ) -> Blog; - // async fn add_bulk(&mut self, blogs: Vec) -> Vec; + ) -> Option; async fn update( &mut self, id: BlogId, @@ -28,6 +27,6 @@ pub trait BlogRepo: DynClone { filename: Option, source: Option, body: Option, - ) -> Blog; - async fn delete(&mut self, id: BlogId) -> BlogDeleted; + ) -> Option; + async fn delete(&mut self, id: BlogId) -> Option; } diff --git a/internal/src/usecase/api.rs b/internal/src/usecase/api.rs index a361d97..49bd2f0 100644 --- a/internal/src/usecase/api.rs +++ b/internal/src/usecase/api.rs @@ -17,10 +17,10 @@ impl Debug for dyn ApiRepo + Send + Sync { #[async_trait] impl ApiQueryPort for ApiUseCase { - async fn list_metadata(&self) -> Vec { + async fn list_metadata(&self) -> Option> { self.api_repo.list_metadata().await } - async fn fetch(&self, metadata: BlogMetadata) -> Blog { + async fn fetch(&self, metadata: BlogMetadata) -> Option { self.api_repo.fetch(metadata).await } } diff --git a/internal/src/usecase/blog.rs b/internal/src/usecase/blog.rs index 1161041..82ae05c 100644 --- a/internal/src/usecase/blog.rs +++ b/internal/src/usecase/blog.rs @@ -20,13 +20,13 @@ impl Debug for dyn BlogRepo + Send + Sync { #[async_trait] impl BlogQueryPort for BlogUseCase { - async fn find(&self, id: BlogId) -> Blog { + async fn find(&self, id: BlogId) -> Option { self.blog_repo.find(id).await } - async fn find_blogs(&self, start: BlogStartPage, end: BlogEndPage) -> Vec { + async fn find_blogs(&self, start: BlogStartPage, end: BlogEndPage) -> Option> { self.blog_repo.find_blogs(start, end).await } - async fn check_id(&self, id: BlogId) -> BlogStored { + async fn check_id(&self, id: BlogId) -> Option { self.blog_repo.check_id(id).await } } @@ -40,7 +40,7 @@ impl BlogCommandPort for BlogUseCase { filename: BlogFilename, source: BlogSource, body: BlogBody, - ) -> Blog { + ) -> Option { self.blog_repo.add(id, name, filename, source, body).await } async fn update( @@ -50,12 +50,12 @@ impl BlogCommandPort for BlogUseCase { filename: Option, source: Option, body: Option, - ) -> Blog { + ) -> Option { self.blog_repo .update(id, name, filename, source, body) .await } - async fn delete(&mut self, id: BlogId) -> BlogDeleted { + async fn delete(&mut self, id: BlogId) -> Option { self.blog_repo.delete(id).await } } From 6c1223ea679cae186e827e4920de39eb25a805f0 Mon Sep 17 00:00:00 2001 From: "husni.zuhdi@accelbyte.net" Date: Thu, 19 Sep 2024 00:43:24 +0700 Subject: [PATCH 3/7] chore: update api and db methods to return option --- internal/src/api/filesystem.rs | 46 +++++---- internal/src/api/github.rs | 13 ++- internal/src/database/memory.rs | 154 +++++++++++++++++------------ internal/src/database/sqlite.rs | 169 ++++++++++++++++++-------------- 4 files changed, 218 insertions(+), 164 deletions(-) diff --git a/internal/src/api/filesystem.rs b/internal/src/api/filesystem.rs index 56641e8..1f2752f 100644 --- a/internal/src/api/filesystem.rs +++ b/internal/src/api/filesystem.rs @@ -16,42 +16,50 @@ pub struct FilesystemApiUseCase { #[async_trait] impl ApiRepo for FilesystemApiUseCase { - async fn list_metadata(&self) -> Vec { + async fn list_metadata(&self) -> Option> { let read_dir = fs::read_dir(self.blogs_dir.clone()); - let blogs_metadata: Vec = match read_dir { + match read_dir { Ok(value) => { - // Collect Blog Filename - value + let metadatas = value .filter_map(|blog_path| { let blog_path_buf = blog_path.expect("Failed to get blog DirEntry").path(); Self::process_blog_path(&self, blog_path_buf) }) // Collect Blog Metadata .map(|blog_filename| Self::process_blog_metadata(&self, blog_filename)) - .collect() + .collect(); + Some(metadatas) } Err(err) => { error!( "Failed to read directory. Returned empty Vector. Error: {}", err ); - let value: Vec = Vec::new(); - value + None } - }; - blogs_metadata + } } - async fn fetch(&self, metadata: BlogMetadata) -> Blog { - let body = Self::process_markdown(metadata.filename.0.clone()) - .expect("Failed to convert markdown to html"); - debug!("Blog Body with Id {}: {}", &metadata.id.0, &body); + async fn fetch(&self, metadata: BlogMetadata) -> Option { + let result = Self::process_markdown(metadata.filename.0.clone()); + match result { + Ok(body) => { + debug!("Blog Body with Id {}: {}", &metadata.id.0, &body); - Blog { - id: metadata.id, - name: metadata.name, - source: BlogSource::Filesystem, - filename: metadata.filename, - body: BlogBody(body), + Some(Blog { + id: metadata.id, + name: metadata.name, + source: BlogSource::Filesystem, + filename: metadata.filename, + body: BlogBody(body), + }) + } + Err(err) => { + error!( + "Failed to process markdown to html for Blog Id {}. Error: {}", + &metadata.id.0, err + ); + None + } } } } diff --git a/internal/src/api/github.rs b/internal/src/api/github.rs index de30011..6972786 100644 --- a/internal/src/api/github.rs +++ b/internal/src/api/github.rs @@ -23,7 +23,7 @@ pub struct GithubApiUseCase { #[async_trait] impl ApiRepo for GithubApiUseCase { - async fn list_metadata(&self) -> Vec { + async fn list_metadata(&self) -> Option> { let trees_result = Self::fetch_github_trees(&self).await; let mut blogs_metadata: Vec = Vec::new(); @@ -43,12 +43,12 @@ impl ApiRepo for GithubApiUseCase { error!("Failed to filter Github Trees result") } }; - blogs_metadata + Some(blogs_metadata) } - async fn fetch(&self, metadata: BlogMetadata) -> Blog { - let content = Self::fetch_github_content(&self, metadata.filename.clone()).await; + async fn fetch(&self, metadata: BlogMetadata) -> Option { + let result = Self::fetch_github_content(&self, metadata.filename.clone()).await; - let blog = match content { + match result { Some(content) => Self::process_github_content(&self, content, metadata), None => { error!( @@ -57,8 +57,7 @@ impl ApiRepo for GithubApiUseCase { ); None } - }; - blog.unwrap() + } } } diff --git a/internal/src/database/memory.rs b/internal/src/database/memory.rs index d5da5b4..c402f54 100644 --- a/internal/src/database/memory.rs +++ b/internal/src/database/memory.rs @@ -1,7 +1,7 @@ use crate::model::blog::*; use crate::repo::blog::BlogRepo; use async_trait::async_trait; -use tracing::{debug, info, warn}; +use tracing::{debug, error, info, warn}; #[derive(Clone)] pub struct MemoryBlogRepo { @@ -10,19 +10,21 @@ pub struct MemoryBlogRepo { #[async_trait] impl BlogRepo for MemoryBlogRepo { - async fn find(&self, id: BlogId) -> Blog { - let result = self - .blogs - .iter() - .filter(|blog| &blog.id == &id) - .next() - .unwrap(); - info!("Blog {} processed.", &result.id); - debug!("Blog HTML {}.", &result.body); - - result.clone() + async fn find(&self, id: BlogId) -> Option { + let result = self.blogs.iter().filter(|blog| &blog.id == &id).next(); + match result { + Some(blog) => { + info!("Blog {} processed.", &blog.id); + debug!("Blog HTML {}.", &blog.body); + Some(blog.clone()) + } + None => { + info!("Blog {} not found. Return None", &id); + None + } + } } - async fn find_blogs(&self, start: BlogStartPage, end: BlogEndPage) -> Vec { + async fn find_blogs(&self, start: BlogStartPage, end: BlogEndPage) -> Option> { let start_seq = if start.0 as usize > self.blogs.len() { warn!("BlogStartPage is greater than Blogs count. Will reset to 0."); 0 @@ -36,23 +38,34 @@ impl BlogRepo for MemoryBlogRepo { } else if (end.0 as usize > self.blogs.len()) && self.blogs.len() < 10 { warn!("BlogEndPage is greater than Blogs count. Will reset to Blogs count or 10, whichever is lesser."); self.blogs.len() + } else if start.0 as usize > end.0 as usize { + warn!("BlogStartPage is greater than BlogEndPage. Will reset to 10."); + self.blogs.len() } else { end.0 as usize }; let result = &self.blogs[start_seq..end_seq]; - result.to_vec() + if result.is_empty() { + info!( + "Blogs started at {} and ended at {} were not found. Return None", + &start.0, &end.0 + ); + None + } else { + Some(result.to_vec()) + } } - async fn check_id(&self, id: BlogId) -> BlogStored { + async fn check_id(&self, id: BlogId) -> Option { let result = self.blogs.iter().filter(|blog| &blog.id == &id).next(); match result { Some(blog) => { info!("Blog {} is in Memory.", &blog.id.0); - BlogStored(true) + Some(BlogStored(true)) } None => { info!("Blog {} is not in Memory.", &id.0); - BlogStored(false) + Some(BlogStored(false)) } } } @@ -63,7 +76,7 @@ impl BlogRepo for MemoryBlogRepo { filename: BlogFilename, source: BlogSource, body: BlogBody, - ) -> Blog { + ) -> Option { let result = Blog { id, name, @@ -74,15 +87,22 @@ impl BlogRepo for MemoryBlogRepo { self.blogs.push(result.clone()); info!("Blog {} added.", &result.id); debug!("Blog HTML {}.", &result.body); - result + Some(result) } - async fn delete(&mut self, id: BlogId) -> BlogDeleted { - let index = self.blogs.iter().position(|blog| &blog.id == &id).unwrap(); - info!("Deleting Blog with Id {}", &index); - - self.blogs.remove(index); - info!("Deleted Blog with Id {}", &index); - BlogDeleted(true) + async fn delete(&mut self, id: BlogId) -> Option { + let result = self.blogs.iter().position(|blog| &blog.id == &id); + match result { + Some(val) => { + info!("Deleting Blog with Id {}", &val); + self.blogs.remove(val); + info!("Deleted Blog with Id {}", &val); + Some(BlogDeleted(true)) + } + None => { + error!("Failed to delete Blog with Id {}. Blog not found.", &id.0); + None + } + } } async fn update( &mut self, @@ -91,48 +111,52 @@ impl BlogRepo for MemoryBlogRepo { filename: Option, source: Option, body: Option, - ) -> Blog { - let result: &mut Blog = self - .blogs - .iter_mut() - .filter(|blog| &blog.id == &id) - .next() - .unwrap(); - match name { - Some(val) => { - debug!("Update Blog {} name from {} to {}", &id, &result.name, &val); - result.name = val - } - None => (), - } - match filename { - Some(val) => { - debug!( - "Update Blog {} filename from {} to {}", - &id, &result.filename, &val - ); - result.filename = val - } - None => (), - } - match source { - Some(val) => { - debug!( - "Update Blog {} source from {} to {}", - &id, &result.source, &val - ); - result.source = val + ) -> Option { + let result: Option<&mut Blog> = self.blogs.iter_mut().filter(|blog| &blog.id == &id).next(); + + match result { + Some(blog) => { + match name { + Some(val) => { + debug!("Update Blog {} name from {} to {}", &id, &blog.name, &val); + blog.name = val + } + None => (), + } + match filename { + Some(val) => { + debug!( + "Update Blog {} filename from {} to {}", + &id, &blog.filename, &val + ); + blog.filename = val + } + None => (), + } + match source { + Some(val) => { + debug!( + "Update Blog {} source from {} to {}", + &id, &blog.source, &val + ); + blog.source = val + } + None => (), + } + match body { + Some(val) => { + debug!("Update Blog {} body from {} to {}", &id, &blog.body, &val); + blog.body = val + } + None => (), + } + Some(blog.clone()) } - None => (), - } - match body { - Some(val) => { - debug!("Update Blog {} body from {} to {}", &id, &result.body, &val); - result.body = val + None => { + error!("Failed to update Blog with Id {}. Blog not found.", &id.0); + None } - None => (), } - result.clone() } } diff --git a/internal/src/database/sqlite.rs b/internal/src/database/sqlite.rs index 265d245..9b3441d 100644 --- a/internal/src/database/sqlite.rs +++ b/internal/src/database/sqlite.rs @@ -2,8 +2,8 @@ use crate::model::blog::*; use crate::repo::blog::BlogRepo; use async_trait::async_trait; use sqlx::sqlite::SqlitePool; -use sqlx::{query, query_as, Sqlite}; -use tracing::{debug, info}; +use sqlx::{query, query_as, Error, Sqlite}; +use tracing::{debug, error, info}; #[derive(Clone)] pub struct SqliteBlogRepo { @@ -12,21 +12,32 @@ pub struct SqliteBlogRepo { #[async_trait] impl BlogRepo for SqliteBlogRepo { - async fn find(&self, id: BlogId) -> Blog { + async fn find(&self, id: BlogId) -> Option { let blog_id = id.0; let prep_query = "SELECT * FROM blogs WHERE id = $1 ORDER BY id"; debug!("Executing query {} for id {}", &prep_query, &blog_id); - let row: Blog = query_as(&prep_query) + let row: Result = query_as(&prep_query) .bind(&blog_id) .fetch_one(&self.pool) - .await - .expect("Failed to execute get query"); - info!("Blog {} processed.", &row.id); - debug!("Blog HTML {}.", &row.body); - row + .await; + + match row { + Ok(blog) => { + info!("Blog {} processed.", &blog.id); + debug!("Blog HTML {}.", &blog.body); + Some(blog) + } + Err(err) => { + error!( + "Failed to get Blog with Id {}. Blog Not Found. Err: {}", + &blog_id, err + ); + None + } + } } - async fn find_blogs(&self, start: BlogStartPage, end: BlogEndPage) -> Vec { + async fn find_blogs(&self, start: BlogStartPage, end: BlogEndPage) -> Option> { let start_seq = start.0; let end_seq = end.0; let limit = end_seq - start_seq; @@ -36,20 +47,31 @@ impl BlogRepo for SqliteBlogRepo { &prep_query, &start_seq, &end_seq, &limit ); - let rows: Vec = query_as(&prep_query) + let rows: Result, Error> = query_as(&prep_query) .bind(&limit) .bind(&start_seq) .fetch_all(&self.pool) - .await - .expect("Failed to execute get query"); - info!("Blogs from {} to {} processed.", &start_seq, &end_seq); - for row in &rows { - info!("Blog {} processed.", &row.id); - debug!("Blog HTML {}.", &row.body); + .await; + + match rows { + Ok(blogs) => { + info!("Blogs from {} to {} processed.", &start_seq, &end_seq); + for row in &blogs { + info!("Blog {} processed.", &row.id); + debug!("Blog HTML {}.", &row.body); + } + Some(blogs) + } + Err(err) => { + error!( + "Failed to get Blogs with Id started at {} and ended at {}. Err: {}", + &start_seq, &end_seq, err + ); + None + } } - rows } - async fn check_id(&self, id: BlogId) -> BlogStored { + async fn check_id(&self, id: BlogId) -> Option { let blog_id = id.0; let prep_query = "SELECT id FROM blogs WHERE id = $1 ORDER BY id"; debug!("Executing query {} for id {}", &prep_query, &blog_id); @@ -60,12 +82,12 @@ impl BlogRepo for SqliteBlogRepo { .await { Ok(id) => { - info!("Blog {} is in Memory.", &id.0); - BlogStored(true) + info!("Blog {} is in SQLite.", &id.0); + Some(BlogStored(true)) } Err(err) => { - info!("Blog {} is not in Memory. Error: {}", &blog_id, err); - BlogStored(false) + info!("Blog {} is not in SQLite. Error: {}", &blog_id, err); + Some(BlogStored(false)) } } } @@ -76,8 +98,8 @@ impl BlogRepo for SqliteBlogRepo { filename: BlogFilename, source: BlogSource, body: BlogBody, - ) -> Blog { - let blog_id = id.0; + ) -> Option { + let blog_id = &id.0; let blog_name = name.0; let blog_filename = filename.0; let blog_source = format!("{}", source); @@ -93,42 +115,45 @@ impl BlogRepo for SqliteBlogRepo { .bind(&blog_source) .bind(&blog_body) .execute(&self.pool) - .await - .expect("Failed to execute add query"); - info!( - "Blog {} in row {} was added.", - &blog_id, - &query_res.rows_affected() - ); + .await; - let prep_get_query = "SELECT * FROM blogs WHERE id = $1 ORDER BY id"; - debug!("Executing query {} for id {}", &prep_get_query, &blog_id); + match query_res { + Ok(row) => { + info!( + "Blog {} in row {} added to SQLite.", + &blog_id, + &row.rows_affected() + ); - let row: Blog = query_as(&prep_get_query) - .bind(&blog_id) - .fetch_one(&self.pool) - .await - .expect("Failed to execute get query"); - info!("Blog {} processed.", &row.id); - debug!("Blog HTML {}.", &row.body); - row + Self::find(&self.clone(), id.clone()).await + } + Err(err) => { + error!("Failed to add Blog with Id {}. Err: {}", &blog_id, err); + None + } + } } - async fn delete(&mut self, id: BlogId) -> BlogDeleted { + async fn delete(&mut self, id: BlogId) -> Option { let blog_id = id.0; let prep_query = "DELETE FROM blogs WHERE id = $1"; debug!("Executing query {} for id {}", &prep_query, &blog_id); - let query_res = query(&prep_query) - .bind(&blog_id) - .execute(&self.pool) - .await - .expect("Failed to execute delete query"); - info!( - "Blog {} in row {} was deleted.", - &blog_id, - &query_res.rows_affected() - ); - BlogDeleted(true) + let query_res = query(&prep_query).bind(&blog_id).execute(&self.pool).await; + + match query_res { + Ok(row) => { + info!( + "Blog {} in row {} was deleted.", + &blog_id, + &row.rows_affected() + ); + Some(BlogDeleted(true)) + } + Err(err) => { + error!("Failed to delete Blog with Id {}. Err: {}", &blog_id, err); + None + } + } } async fn update( &mut self, @@ -137,8 +162,8 @@ impl BlogRepo for SqliteBlogRepo { filename: Option, source: Option, body: Option, - ) -> Blog { - let blog_id = id.0; + ) -> Option { + let blog_id = &id.0; let mut affected_col = "".to_string(); match name { Some(val) => { @@ -182,25 +207,23 @@ impl BlogRepo for SqliteBlogRepo { let query_res = query(&prep_update_query) .bind(&blog_id) .execute(&self.pool) - .await - .expect("Failed to execute update query"); - info!( - "Blog {} in row {} was updated.", - &blog_id, - &query_res.rows_affected() - ); + .await; - let prep_get_query = "SELECT * FROM blogs WHERE id = $1 ORDER BY id"; - debug!("Executing query {} for id {}", &prep_get_query, &blog_id); + match query_res { + Ok(row) => { + info!( + "Blog {} in row {} was updated on SQLite.", + &blog_id, + &row.rows_affected() + ); - let row: Blog = query_as(&prep_get_query) - .bind(&blog_id) - .fetch_one(&self.pool) - .await - .expect("Failed to execute get query"); - info!("Blog {} processed.", &row.id); - debug!("Blog HTML {}.", &row.body); - row + Self::find(&self.clone(), id.clone()).await + } + Err(err) => { + error!("Failed to update Blog with Id {}. Err: {}", &blog_id, err); + None + } + } } } From 27eef4ca7857f93aade1e5a3effbe1d8616f1ce3 Mon Sep 17 00:00:00 2001 From: "husni.zuhdi@accelbyte.net" Date: Thu, 19 Sep 2024 00:44:06 +0700 Subject: [PATCH 4/7] feat: add filesystem_dir envar to accomodate blogs filesystem direcotry envar --- internal/src/config.rs | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/internal/src/config.rs b/internal/src/config.rs index 541a674..42ca618 100644 --- a/internal/src/config.rs +++ b/internal/src/config.rs @@ -10,6 +10,7 @@ pub struct Config { pub environment: String, pub data_source: String, pub database_url: String, + pub filesystem_dir: String, pub gh_owner: String, pub gh_repo: String, pub gh_branch: String, @@ -24,7 +25,8 @@ impl Default for Config { let log_level = tracing::Level::INFO; let environment: String = "release".to_string(); let data_source: String = "memory".to_string(); - let database_url: String = "".to_owned(); + let database_url: String = "".to_string(); + let filesystem_dir: String = "".to_string(); let gh_owner: String = "".to_string(); let gh_repo: String = "".to_string(); let gh_branch: String = "".to_string(); @@ -36,6 +38,7 @@ impl Default for Config { environment, data_source, database_url, + filesystem_dir, gh_owner, gh_repo, gh_branch, @@ -89,6 +92,7 @@ impl Config { let environment: String = Self::parse_optional_envar("ENVIRONMENT", "release"); let data_source: String = Self::parse_optional_envar("DATA_SOURCE", "memory"); let database_url: String = Self::parse_optional_envar("DATABASE_URL", ""); + let filesystem_dir: String = Self::parse_optional_envar("FILESYSTEM_DIR", ""); let gh_owner: String = Self::parse_optional_envar("GITHUB_OWNER", ""); let gh_repo: String = Self::parse_optional_envar("GITHUB_REPO", ""); let gh_branch: String = Self::parse_optional_envar("GITHUB_BRANCH", ""); @@ -100,6 +104,7 @@ impl Config { environment, data_source, database_url, + filesystem_dir, gh_owner, gh_repo, gh_branch, @@ -119,6 +124,7 @@ mod test { let environment: String = "release".to_string(); let data_source: String = "memory".to_string(); let database_url: String = "".to_string(); + let filesystem_dir: String = "".to_string(); let gh_owner: String = "".to_string(); let gh_repo: String = "".to_string(); let gh_branch: String = "".to_string(); @@ -131,6 +137,7 @@ mod test { assert_eq!(result.environment, environment); assert_eq!(result.data_source, data_source); assert_eq!(result.database_url, database_url); + assert_eq!(result.filesystem_dir, filesystem_dir); assert_eq!(result.gh_owner, gh_owner); assert_eq!(result.gh_repo, gh_repo); assert_eq!(result.gh_branch, gh_branch); @@ -147,6 +154,7 @@ mod test { let data_source = ""; let expected_data_source = "memory"; let database_url = ""; + let filesystem_dir = ""; let gh_owner = ""; let gh_repo = ""; let gh_branch = ""; @@ -158,6 +166,7 @@ mod test { environment, data_source, database_url, + filesystem_dir, gh_owner, gh_repo, gh_branch, @@ -171,6 +180,7 @@ mod test { assert_eq!(result.environment, expected_environment); assert_eq!(result.data_source, expected_data_source); assert_eq!(result.database_url, database_url); + assert_eq!(result.filesystem_dir, filesystem_dir); assert_eq!(result.gh_owner, gh_owner); assert_eq!(result.gh_repo, gh_repo); assert_eq!(result.gh_branch, gh_branch); @@ -187,6 +197,7 @@ mod test { let environment = "dev"; let data_source = "sqlite"; let database_url = "sqlite:husni-portfolio.db"; + let filesystem_dir = ""; let gh_owner = "husni-zuhdi"; let gh_repo = "husni-blog-resources"; let gh_branch = "main"; @@ -198,6 +209,7 @@ mod test { environment, data_source, database_url, + filesystem_dir, gh_owner, gh_repo, gh_branch, @@ -211,6 +223,7 @@ mod test { assert_eq!(result.environment, environment); assert_eq!(result.data_source, data_source); assert_eq!(result.database_url, database_url); + assert_eq!(result.filesystem_dir, filesystem_dir); assert_eq!(result.gh_owner, gh_owner); assert_eq!(result.gh_repo, gh_repo); assert_eq!(result.gh_branch, gh_branch); @@ -225,6 +238,7 @@ mod test { environment: &str, data_source: &str, database_url: &str, + filesystem_dir: &str, gh_owner: &str, gh_repo: &str, gh_branch: &str, @@ -235,6 +249,7 @@ mod test { env::set_var("ENVIRONMENT", environment); env::set_var("DATA_SOURCE", data_source); env::set_var("DATABASE_URL", database_url); + env::set_var("FILESYSTEM_DIR", filesystem_dir); env::set_var("GITHUB_OWNER", gh_owner); env::set_var("GITHUB_REPO", gh_repo); env::set_var("GITHUB_BRANCH", gh_branch); @@ -247,6 +262,7 @@ mod test { env::remove_var("ENVIRONMENT"); env::remove_var("DATA_SOURCE"); env::remove_var("DATABASE_URL"); + env::remove_var("FILESYSTEM_DIR"); env::remove_var("GITHUB_OWNER"); env::remove_var("GITHUB_REPO"); env::remove_var("GITHUB_BRANCH"); From d8cd9c741a4214d8122d5b898c4279d447fd7b5c Mon Sep 17 00:00:00 2001 From: "husni.zuhdi@accelbyte.net" Date: Thu, 19 Sep 2024 00:44:48 +0700 Subject: [PATCH 5/7] chore: change filesystem usecase flow and implement new option returned from api and db methods --- internal/src/state.rs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/internal/src/state.rs b/internal/src/state.rs index 2059504..29bbe3d 100644 --- a/internal/src/state.rs +++ b/internal/src/state.rs @@ -27,8 +27,10 @@ pub async fn state_factory(config: Config) -> AppState { BlogUseCase::new(Box::new(repo)) }; - let fs_usecase = FilesystemApiUseCase::new("./statics/blogs/".to_string()).await; - let _ = populate_blog(Box::new(fs_usecase), &mut blog_uc).await; + if !config.filesystem_dir.is_empty() { + let fs_usecase = FilesystemApiUseCase::new(config.filesystem_dir.clone()).await; + let _ = populate_blog(Box::new(fs_usecase), &mut blog_uc).await; + } if github_api_is_enabled { let github_usecase = GithubApiUseCase::new( @@ -49,13 +51,13 @@ pub async fn state_factory(config: Config) -> AppState { } async fn populate_blog(api_uc: Box, blog_uc: &mut BlogUseCase) { - let blogs_metadata = api_uc.list_metadata().await; + let blogs_metadata = api_uc.list_metadata().await.unwrap(); for metadata in blogs_metadata { - let blog_is_not_stored = !blog_uc.check_id(metadata.id.clone()).await.0; + let blog_is_not_stored = !blog_uc.check_id(metadata.id.clone()).await.unwrap().0; if blog_is_not_stored { info!("Start to populate Blog {}.", &metadata.id); debug!("Start to fetch Blog {}.", &metadata.id); - let blog = api_uc.fetch(metadata.clone()).await; + let blog = api_uc.fetch(metadata.clone()).await.unwrap(); debug!("Finished to fetch Blog {}.", &metadata.id); debug!("Start to store Blog {}.", &metadata.id); From a8603c9c97489d3e5578b43b32335149ca37c3aa Mon Sep 17 00:00:00 2001 From: "husni.zuhdi@accelbyte.net" Date: Thu, 19 Sep 2024 00:45:22 +0700 Subject: [PATCH 6/7] chore: implement optional value returned by db methods --- internal/src/handler/blog.rs | 89 ++++++++++++++++++++++-------------- 1 file changed, 54 insertions(+), 35 deletions(-) diff --git a/internal/src/handler/blog.rs b/internal/src/handler/blog.rs index 34fea68..79a09ed 100644 --- a/internal/src/handler/blog.rs +++ b/internal/src/handler/blog.rs @@ -1,4 +1,4 @@ -use crate::handler::status::get_500_internal_server_error; +use crate::handler::status::{get_404_not_found, get_500_internal_server_error}; use crate::model::blog::{BlogEndPage, BlogId, BlogPagination, BlogStartPage}; use crate::model::{ axum::AppState, @@ -39,27 +39,38 @@ pub async fn get_blogs( }; // Construct BlogsTemplate Struct - let blogs_data = data.blog_repo.find_blogs(start, end).await; - let blogs: Vec = blogs_data - .iter() - .map(|blog| { - debug!("Construct BlogsTemplateBlog for Blog Id {}", &blog.id); - BlogsTemplateBlog { - id: &blog.id.as_str(), - name: &blog.name.as_str(), - } - }) - .collect(); - debug!("BlogsTemplate blogs : {:?}", &blogs); + let result = data.blog_repo.find_blogs(start.clone(), end.clone()).await; + match result { + Some(blogs_data) => { + let blogs: Vec = blogs_data + .iter() + .map(|blog| { + debug!("Construct BlogsTemplateBlog for Blog Id {}", &blog.id); + BlogsTemplateBlog { + id: &blog.id.as_str(), + name: &blog.name.as_str(), + } + }) + .collect(); + debug!("BlogsTemplate blogs : {:?}", &blogs); - let blogs_res = BlogsTemplate { blogs: &blogs }.render(); - match blogs_res { - Ok(res) => { - info!("Blogs askama template rendered."); - Html(res) + let blogs_res = BlogsTemplate { blogs: &blogs }.render(); + match blogs_res { + Ok(res) => { + info!("Blogs askama template rendered."); + Html(res) + } + Err(err) => { + error!("Failed to render get_blogs.html. {}", err); + get_500_internal_server_error() + } + } } - Err(err) => { - error!("Failed to render get_blogs.html. {}", err); + None => { + error!( + "Failed to find blogs with Blog Id started at {} and ended at {}.", + &start.0, &end.0 + ); get_500_internal_server_error() } } @@ -74,23 +85,31 @@ pub async fn get_blog(Path(path): Path, State(app_state): State { + let blog = BlogTemplate { + id: path.clone().as_str(), + name: &blog_data.name.as_str(), + filename: &blog_data.filename.as_str(), + body: &blog_data.body.as_str(), + } + .render(); - match blog { - Ok(res) => { - info!("Blog ID {} askama template rendered.", &path); - Html(res) + match blog { + Ok(res) => { + info!("Blog ID {} askama template rendered.", &path); + Html(res) + } + Err(err) => { + error!("Failed to render blog.html. {}", err); + get_500_internal_server_error() + } + } } - Err(err) => { - error!("Failed to render blog.html. {}", err); - get_500_internal_server_error() + None => { + error!("Failed to find Blog with Id {}.", &path); + get_404_not_found().await } } } From b01816df08a39a2276894935d9769eca07f52517 Mon Sep 17 00:00:00 2001 From: "husni.zuhdi@accelbyte.net" Date: Thu, 19 Sep 2024 00:45:39 +0700 Subject: [PATCH 7/7] chore: bump app version --- version.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/version.json b/version.json index 785e8ce..ce568df 100644 --- a/version.json +++ b/version.json @@ -1,5 +1,5 @@ { - "version": "0.1.3", - "build_date": "2024-08-28", - "build_hash": "7cce9db6e7d81b895080e8e7c9d25f1ce4733723" + "version": "0.2.0", + "build_date": "2024-09-19", + "build_hash": "2c7ffdde74efcd069372657947f30ccf82754317" }