saleor-apps-rs/sitemap-generator/src/app.rs

167 lines
5 KiB
Rust
Raw Normal View History

2024-03-06 15:42:04 +00:00
use anyhow::bail;
use axum::{
http::StatusCode,
response::{IntoResponse, Response},
};
use chrono::{DateTime, FixedOffset};
2024-03-14 13:51:55 +00:00
use std::{sync::Arc, time::Duration};
use tracing_subscriber::EnvFilter;
2024-03-06 15:42:04 +00:00
2024-03-14 13:51:55 +00:00
use redis::{AsyncCommands, Client};
2024-03-06 15:42:04 +00:00
use saleor_app_sdk::{config::Config, manifest::AppManifest, SaleorApp};
use serde::{Deserialize, Serialize};
use tracing::{debug, info, level_filters::LevelFilter};
2024-03-06 15:42:04 +00:00
// Make our own error that wraps `anyhow::Error`.
pub struct AppError(anyhow::Error);
// Tell axum how to convert `AppError` into a response.
impl IntoResponse for AppError {
fn into_response(self) -> Response {
(
StatusCode::INTERNAL_SERVER_ERROR,
format!("Something went wrong: {}", self.0),
)
.into_response()
}
}
// This enables using `?` on functions that return `Result<_, anyhow::Error>` to turn them into
// `Result<_, AppError>`. That way you don't need to do that manually.
impl<E> From<E> for AppError
where
E: Into<anyhow::Error>,
{
fn from(err: E) -> Self {
Self(err.into())
}
}
pub fn trace_to_std(config: &Config) -> anyhow::Result<()> {
let filter = EnvFilter::builder()
.with_default_directive(LevelFilter::DEBUG.into())
.from_env()?
.add_directive(format!("{}={}", env!("CARGO_PKG_NAME"), config.log_level).parse()?);
2024-03-06 15:42:04 +00:00
tracing_subscriber::fmt()
.with_max_level(config.log_level)
.with_env_filter(filter)
.with_target(true)
.compact()
2024-03-06 15:42:04 +00:00
.init();
Ok(())
2024-03-06 15:42:04 +00:00
}
/**
* Sitemaps have a limit of 10mb, so we create an index and split all paths between multiple
* sitemaps.
*/
#[derive(Debug, Clone)]
pub struct AppState {
pub xml_cache: Arc<tokio::sync::Mutex<XmlCache>>,
2024-03-06 15:42:04 +00:00
pub saleor_app: Arc<tokio::sync::Mutex<SaleorApp>>,
pub config: Config,
pub sitemap_config: SitemapConfig,
pub manifest: AppManifest,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SitemapConfig {
#[serde(rename = "sitemap_target_folder")]
pub target_folder: String,
#[serde(rename = "sitemap_product_template")]
pub product_template: String,
#[serde(rename = "sitemap_category_template")]
pub category_template: String,
#[serde(rename = "sitemap_pages_template")]
pub pages_template: String,
#[serde(rename = "sitemap_collection_template")]
pub collection_template: String,
2024-03-06 15:42:04 +00:00
#[serde(rename = "sitemap_index_hostname")]
pub index_hostname: String,
}
impl SitemapConfig {
pub fn load() -> Result<Self, envy::Error> {
_ = dotenvy::dotenv();
2024-03-14 13:51:55 +00:00
envy::from_env::<SitemapConfig>()
2024-03-06 15:42:04 +00:00
}
}
#[derive(Debug)]
2024-03-06 15:42:04 +00:00
pub struct XmlCache {
client: Client,
app_api_base_url: String,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
2024-03-06 15:42:04 +00:00
pub struct XmlData {
pub id: cynic::Id,
pub slug: String,
pub relations: Vec<cynic::Id>,
pub data_type: XmlDataType,
pub last_modified: DateTime<FixedOffset>,
2024-03-06 15:42:04 +00:00
}
#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
2024-03-06 15:42:04 +00:00
pub enum XmlDataType {
Category,
Product,
Page,
Collection,
}
impl XmlCache {
pub fn new(redis_url: &str, app_api_base_url: &str) -> anyhow::Result<Self> {
debug!("creating XmlCache...");
let client = redis::Client::open(redis_url)?;
let mut conn = client.get_connection_with_timeout(Duration::from_secs(3))?;
let val: Result<String, redis::RedisError> =
redis::cmd("INFO").arg("server").query(&mut conn);
match val {
Ok(_) => Ok(Self {
client,
app_api_base_url: app_api_base_url.to_owned(),
}),
Err(e) => bail!("failed redis connection(XmlCache), {:?}", e),
}
}
/**
* ONLY USE IF YOU KNOW WHAT YOU'RE DOING! Will flush entire cache, run regenerate() from
* webhooks to renew.
*/
pub async fn delete_all(&self, saleor_api_url: &str) -> anyhow::Result<()> {
debug!("xml data delete_cache()");
2024-04-15 17:51:37 +00:00
let mut conn = self.client.get_multiplexed_async_connection().await?;
conn.del(self.prepare_key(saleor_api_url)).await?;
info!("sucessful cache wipe");
Ok(())
}
2024-03-06 15:42:04 +00:00
pub async fn get_all(&self, saleor_api_url: &str) -> anyhow::Result<Vec<XmlData>> {
debug!("xml data get_all()");
2024-04-15 17:51:37 +00:00
let mut conn = self.client.get_multiplexed_async_connection().await?;
let res: Vec<u8> = conn.get(self.prepare_key(saleor_api_url)).await?;
let cache: Vec<XmlData> = serde_cbor::from_slice(&res)?;
2024-03-06 15:42:04 +00:00
info!("sucessful cache get");
Ok(cache)
}
pub async fn set(&self, data: Vec<XmlData>, saleor_api_url: &str) -> anyhow::Result<()> {
debug!("xml data set()");
2024-04-15 17:51:37 +00:00
let mut conn = self.client.get_multiplexed_async_connection().await?;
conn.set(self.prepare_key(saleor_api_url), serde_cbor::to_vec(&data)?)
.await?;
2024-03-06 15:42:04 +00:00
info!("sucessful cache set");
Ok(())
}
pub fn prepare_key(&self, saleor_api_url: &str) -> String {
let key = format!("{}:{saleor_api_url}", self.app_api_base_url);
key
}
}