Skip to content

Commit

Permalink
feat(websites): store navigation items query in cache
Browse files Browse the repository at this point in the history
  • Loading branch information
javierEd committed Feb 14, 2025
1 parent ec88b41 commit 9542908
Show file tree
Hide file tree
Showing 7 changed files with 122 additions and 38 deletions.
1 change: 1 addition & 0 deletions mango3-core/src/constants.rs
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,7 @@ pub(crate) const PREFIX_GET_USER_PASSWORD_RESET_GET_BY_USER: &str = "get_user_pa
pub(crate) const PREFIX_GET_USER_SESSION_BY_ID: &str = "get_user_session_by_id";
pub(crate) const PREFIX_GET_WEBSITE_BY_ID: &str = "get_website_by_id";
pub(crate) const PREFIX_GET_WEBSITE_BY_SUBDOMAIN: &str = "get_website_by_subdomain";
pub(crate) const PREFIX_NAVIGATION_ITEM_ALL_BY_WEBSITE: &str = "navigation_item_all_by_website";
pub(crate) const PREFIX_POST_COMMENT_CONTENT_HTML: &str = "post_comment_content_html";
pub(crate) const PREFIX_POST_CONTENT_HTML: &str = "post_content_html";
pub(crate) const PREFIX_POST_CONTENT_PREVIEW_HTML: &str = "post_content_preview_html";
Expand Down
81 changes: 47 additions & 34 deletions mango3-core/src/models/navigation_item/mod.rs
Original file line number Diff line number Diff line change
@@ -1,17 +1,25 @@
use std::fmt::Display;

use serde::{Deserialize, Serialize};
use sqlx::types::chrono::{DateTime, Utc};
use sqlx::types::Uuid;
use sqlx::{query, query_as};

use crate::constants::PREFIX_NAVIGATION_ITEM_ALL_BY_WEBSITE;
use crate::enums::Input;
use crate::validator::{ValidationErrors, Validator, ValidatorTrait};
use crate::CoreContext;

use super::Website;
use super::{AsyncRedisCacheTrait, Website};

mod navigation_item_all;
mod navigation_item_insert;
mod navigation_item_save_all;
mod navigation_item_update;

use navigation_item_all::NAVIGATION_ITEM_ALL_BY_WEBSITE;

#[derive(Clone, Deserialize, Serialize)]
pub struct NavigationItem {
pub id: Uuid,
pub website_id: Uuid,
Expand All @@ -22,16 +30,40 @@ pub struct NavigationItem {
pub updated_at: Option<DateTime<Utc>>,
}

impl NavigationItem {
pub async fn all_by_website(core_context: &CoreContext, website: &Website) -> Vec<Self> {
query_as!(
Self,
"SELECT * FROM navigation_items WHERE website_id = $1 ORDER BY position ASC",
website.id // $1
#[derive(Clone, Deserialize, Serialize)]
struct NavigationItems(Vec<NavigationItem>);

impl Display for NavigationItems {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}",
self.0
.iter()
.map(|item| item.id.to_string())
.collect::<Vec<String>>()
.join(", ")
)
.fetch_all(&core_context.db_pool)
.await
.unwrap_or_default()
}
}

impl From<NavigationItems> for Vec<NavigationItem> {
fn from(items: NavigationItems) -> Self {
items.0
}
}

impl From<Vec<NavigationItem>> for NavigationItems {
fn from(items: Vec<NavigationItem>) -> Self {
NavigationItems(items)
}
}

impl NavigationItem {
async fn cache_remove_by_website(website: &Website) {
NAVIGATION_ITEM_ALL_BY_WEBSITE
.cache_remove(PREFIX_NAVIGATION_ITEM_ALL_BY_WEBSITE, &website.id)
.await;
}

pub async fn delete_all(
Expand All @@ -46,8 +78,11 @@ impl NavigationItem {
)
.execute(&core_context.db_pool)
.await
.map(|_| ())
.map_err(|_| ValidationErrors::default())
.map_err(|_| ValidationErrors::default())?;

Self::cache_remove_by_website(website).await;

Ok(())
}

pub async fn get_by_id(core_context: &CoreContext, id: Uuid, website: Option<&Website>) -> sqlx::Result<Self> {
Expand Down Expand Up @@ -75,28 +110,6 @@ mod tests {

use super::NavigationItem;

#[tokio::test]
async fn should_get_zero_navigation_items() {
let core_context = setup_core_context().await;
let website = insert_test_website(&core_context, None).await;

let items = NavigationItem::all_by_website(&core_context, &website).await;

assert!(items.is_empty());
}

#[tokio::test]
async fn should_get_one_navigation_item() {
let core_context = setup_core_context().await;
let website = insert_test_website(&core_context, None).await;

insert_test_navigation_item(&core_context, Some(&website)).await;

let items = NavigationItem::all_by_website(&core_context, &website).await;

assert_eq!(items.len(), 1);
}

#[tokio::test]
async fn should_delete_all_navigation_items() {
let core_context = setup_core_context().await;
Expand Down
68 changes: 68 additions & 0 deletions mango3-core/src/models/navigation_item/navigation_item_all.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
use cached::proc_macro::io_cached;
use cached::AsyncRedisCache;
use sqlx::query_as;
use sqlx::types::Uuid;

use crate::constants::PREFIX_NAVIGATION_ITEM_ALL_BY_WEBSITE;
use crate::models::{async_redis_cache, Website};
use crate::CoreContext;

use super::{NavigationItem, NavigationItems};

impl NavigationItem {
pub async fn all_by_website(core_context: &CoreContext, website: &Website) -> Vec<Self> {
navigation_item_all_by_website(core_context, website)
.await
.map(|items| items.into())
.unwrap_or_default()
}
}

#[io_cached(
map_error = r##"|_| sqlx::Error::RowNotFound"##,
convert = r#"{ website.id }"#,
ty = "AsyncRedisCache<Uuid, NavigationItems>",
create = r##" { async_redis_cache(PREFIX_NAVIGATION_ITEM_ALL_BY_WEBSITE).await } "##
)]
pub(crate) async fn navigation_item_all_by_website(
core_context: &CoreContext,
website: &Website,
) -> sqlx::Result<NavigationItems> {
query_as!(
NavigationItem,
"SELECT * FROM navigation_items WHERE website_id = $1 ORDER BY position ASC",
website.id // $1
)
.fetch_all(&core_context.db_pool)
.await
.map(|items| items.into())
}

#[cfg(test)]
mod tests {
use crate::test_utils::{insert_test_navigation_item, insert_test_website, setup_core_context};

use super::NavigationItem;

#[tokio::test]
async fn should_get_zero_navigation_items() {
let core_context = setup_core_context().await;
let website = insert_test_website(&core_context, None).await;

let items = NavigationItem::all_by_website(&core_context, &website).await;

assert!(items.is_empty());
}

#[tokio::test]
async fn should_get_one_navigation_item() {
let core_context = setup_core_context().await;
let website = insert_test_website(&core_context, None).await;

insert_test_navigation_item(&core_context, Some(&website)).await;

let items = NavigationItem::all_by_website(&core_context, &website).await;

assert_eq!(items.len(), 1);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use crate::CoreContext;
use super::NavigationItem;

impl NavigationItem {
pub async fn insert(
pub(crate) async fn insert(
core_context: &CoreContext,
website: &Website,
position: i16,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ impl NavigationItem {

let _ = Self::delete_all(core_context, skip_from_removal, website).await;

Self::cache_remove_by_website(website).await;

Ok(())
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use crate::CoreContext;
use super::NavigationItem;

impl NavigationItem {
pub async fn update(
pub(crate) async fn update(
&self,
core_context: &CoreContext,
position: i16,
Expand Down
4 changes: 2 additions & 2 deletions mango3-websites/src/components/website_top_bar.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ pub fn WebsiteTopBar() -> impl IntoView {
class="bg-base-200"
right_items=move || view! { <GoToMango3 /> }
>
<Suspense>
<Transition>
{move || Suspend::new(async move {
navigation_items_resource
.get()
Expand All @@ -62,7 +62,7 @@ pub fn WebsiteTopBar() -> impl IntoView {
}
})
})}
</Suspense>
</Transition>
</TopBar>
}
}

0 comments on commit 9542908

Please sign in to comment.