2024-07-14 20:28:21 +00:00
|
|
|
pub mod event_handler;
|
|
|
|
pub mod regenerate;
|
2024-07-03 14:07:04 +00:00
|
|
|
|
2024-07-10 21:51:59 +00:00
|
|
|
use std::ops::{Deref, DerefMut};
|
|
|
|
|
2024-07-16 15:46:00 +00:00
|
|
|
use saleor_app_sdk::webhooks::{utils::EitherWebhookType, AsyncWebhookEventType};
|
2024-07-03 14:07:04 +00:00
|
|
|
use serde::{Deserialize, Serialize};
|
2024-07-10 21:51:59 +00:00
|
|
|
use tinytemplate::TinyTemplate;
|
2024-07-15 14:04:35 +00:00
|
|
|
use tracing::debug;
|
2024-07-10 21:51:59 +00:00
|
|
|
|
2024-07-16 15:46:00 +00:00
|
|
|
use crate::{
|
|
|
|
app::SitemapConfig,
|
|
|
|
queries::event_subjects_updated::{
|
|
|
|
Category, Category2, CategoryCreated, CategoryDeleted, Collection, CollectionCreated,
|
|
|
|
CollectionDeleted, Page, PageCreated, PageDeleted, Product, ProductCreated, ProductDeleted,
|
|
|
|
},
|
|
|
|
};
|
2024-07-03 14:07:04 +00:00
|
|
|
|
|
|
|
const SITEMAP_XMLNS: &str = "http://sitemaps.org/schemas/sitemap/0.9";
|
|
|
|
const SALEOR_REF_XMLNS: &str = "http://app-sitemap-generator.kremik.sk/xml-schemas/saleor-ref.xsd";
|
|
|
|
|
2024-07-08 18:20:36 +00:00
|
|
|
#[derive(Serialize, Deserialize, PartialEq, Eq, Debug, Clone)]
|
2024-07-03 14:07:04 +00:00
|
|
|
#[serde(rename = "urlset")]
|
|
|
|
pub struct UrlSet {
|
2024-07-10 21:51:59 +00:00
|
|
|
pub urls: Vec<Url>,
|
2024-07-03 14:07:04 +00:00
|
|
|
}
|
|
|
|
|
2024-07-08 18:20:36 +00:00
|
|
|
#[derive(Serialize, Deserialize, PartialEq, Eq, Debug, Clone)]
|
2024-07-03 14:07:04 +00:00
|
|
|
pub struct Url {
|
2024-07-10 21:51:59 +00:00
|
|
|
pub url: String,
|
|
|
|
pub data: ItemData,
|
|
|
|
pub related: Option<ItemData>,
|
2024-07-03 14:07:04 +00:00
|
|
|
}
|
2024-07-08 18:20:36 +00:00
|
|
|
|
|
|
|
#[derive(Serialize, Deserialize, PartialEq, Eq, Debug, Clone)]
|
2024-07-10 21:51:59 +00:00
|
|
|
pub struct ItemData {
|
|
|
|
pub id: String,
|
|
|
|
pub slug: String,
|
|
|
|
pub typ: ItemType,
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Serialize, Deserialize, PartialEq, Eq, Debug, Clone)]
|
|
|
|
pub enum ItemType {
|
2024-07-03 14:07:04 +00:00
|
|
|
Product,
|
2024-07-08 18:20:36 +00:00
|
|
|
Category,
|
|
|
|
Collection,
|
|
|
|
Page,
|
2024-07-03 14:07:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl UrlSet {
|
|
|
|
pub fn new() -> Self {
|
2024-07-10 21:51:59 +00:00
|
|
|
Self { urls: vec![] }
|
|
|
|
}
|
2024-07-11 17:21:50 +00:00
|
|
|
|
|
|
|
pub fn flush_related(&mut self, id: &str) {
|
|
|
|
self.retain(|u| u.data.id != id && u.related.as_ref().map_or(true, |ud| ud.id != id));
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn find_related(&mut self, id: &str) -> Vec<&mut Url> {
|
|
|
|
self.iter_mut()
|
|
|
|
.filter(|u| u.data.id == id || u.related.as_ref().map_or(false, |ud| ud.id == id))
|
|
|
|
.collect()
|
|
|
|
}
|
|
|
|
|
2024-07-16 15:46:00 +00:00
|
|
|
pub fn find_affected(&mut self, id: &str, slug: &str) -> AffectedResult<'_> {
|
|
|
|
let related: Vec<&mut Url> = self.find_related(id);
|
|
|
|
debug!("related urls: {:?}", &related);
|
|
|
|
if related.is_empty() {
|
|
|
|
return AffectedResult::NoneRelated;
|
|
|
|
}
|
|
|
|
|
|
|
|
let affected = related
|
|
|
|
.into_iter()
|
2024-07-11 17:21:50 +00:00
|
|
|
.filter(|u| {
|
2024-07-15 14:04:35 +00:00
|
|
|
(u.data.id == id && u.data.slug != slug)
|
2024-07-16 15:46:00 +00:00
|
|
|
|| u.related
|
2024-07-11 17:21:50 +00:00
|
|
|
.as_ref()
|
2024-07-16 15:46:00 +00:00
|
|
|
.map_or(false, |r| (r.id == id && r.slug != slug))
|
2024-07-11 17:21:50 +00:00
|
|
|
})
|
2024-07-16 15:46:00 +00:00
|
|
|
.map(|u| match u.data.id == id {
|
|
|
|
true => AffectedType::Data(u),
|
|
|
|
false => AffectedType::RelatedData(u),
|
|
|
|
})
|
|
|
|
.collect::<Vec<_>>();
|
|
|
|
if affected.is_empty() {
|
|
|
|
return AffectedResult::NoneAffected;
|
|
|
|
}
|
|
|
|
|
|
|
|
AffectedResult::Some(affected)
|
2024-07-11 17:21:50 +00:00
|
|
|
}
|
2024-07-10 21:51:59 +00:00
|
|
|
}
|
|
|
|
|
2024-07-16 15:46:00 +00:00
|
|
|
#[derive(Debug)]
|
|
|
|
pub enum AffectedResult<'a> {
|
|
|
|
Some(Vec<AffectedType<&'a mut Url>>),
|
|
|
|
NoneAffected,
|
|
|
|
NoneRelated,
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Debug)]
|
|
|
|
pub enum AffectedType<T> {
|
|
|
|
Data(T),
|
|
|
|
RelatedData(T),
|
|
|
|
}
|
|
|
|
|
2024-07-10 21:51:59 +00:00
|
|
|
impl Deref for UrlSet {
|
|
|
|
type Target = Vec<Url>;
|
|
|
|
fn deref(&self) -> &Self::Target {
|
|
|
|
&self.urls
|
2024-07-03 14:07:04 +00:00
|
|
|
}
|
2024-07-10 21:51:59 +00:00
|
|
|
}
|
2024-07-08 18:20:36 +00:00
|
|
|
|
2024-07-10 21:51:59 +00:00
|
|
|
impl DerefMut for UrlSet {
|
|
|
|
fn deref_mut(&mut self) -> &mut Self::Target {
|
|
|
|
&mut self.urls
|
2024-07-08 18:20:36 +00:00
|
|
|
}
|
2024-07-03 14:07:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl Url {
|
2024-07-14 20:28:21 +00:00
|
|
|
pub fn new<T: Serialize>(
|
|
|
|
data: T,
|
|
|
|
sitemap_config: &SitemapConfig,
|
|
|
|
item: ItemData,
|
|
|
|
rel_item: Option<ItemData>,
|
|
|
|
) -> Result<Self, NewUrlError> {
|
2024-07-10 21:51:59 +00:00
|
|
|
let mut tt = TinyTemplate::new();
|
|
|
|
|
2024-07-14 20:28:21 +00:00
|
|
|
tt.add_template(
|
|
|
|
"t",
|
|
|
|
match item.typ {
|
|
|
|
ItemType::Category => &sitemap_config.category_template,
|
|
|
|
ItemType::Page => &sitemap_config.pages_template,
|
|
|
|
ItemType::Collection => &sitemap_config.collection_template,
|
|
|
|
ItemType::Product => &sitemap_config.product_template,
|
|
|
|
},
|
|
|
|
)?;
|
|
|
|
let url = tt.render("t", &data)?;
|
2024-07-10 21:51:59 +00:00
|
|
|
Ok(Self {
|
|
|
|
url,
|
2024-07-14 20:28:21 +00:00
|
|
|
data: item,
|
|
|
|
related: rel_item,
|
2024-07-10 21:51:59 +00:00
|
|
|
})
|
|
|
|
}
|
2024-07-16 15:46:00 +00:00
|
|
|
pub fn into_event_updated_body(self, slug_postfix: &str) -> (String, EitherWebhookType) {
|
|
|
|
match self.data.typ.clone() {
|
|
|
|
ItemType::Product => {
|
|
|
|
let mut data: ProductCreated = self.into();
|
|
|
|
data.product = data.product.map(|mut p| {
|
|
|
|
p.slug = p.slug.clone() + slug_postfix;
|
|
|
|
p
|
|
|
|
});
|
|
|
|
// debug!("{:?}", &data);
|
|
|
|
(
|
|
|
|
serde_json::to_string_pretty(&data).unwrap(),
|
|
|
|
EitherWebhookType::Async(AsyncWebhookEventType::ProductUpdated),
|
|
|
|
)
|
|
|
|
}
|
|
|
|
ItemType::Category => {
|
|
|
|
let mut data: CategoryCreated = self.into();
|
|
|
|
data.category = data.category.map(|mut p| {
|
|
|
|
p.slug = p.slug.clone() + slug_postfix;
|
|
|
|
p
|
|
|
|
});
|
|
|
|
(
|
|
|
|
serde_json::to_string_pretty(&data).unwrap(),
|
|
|
|
EitherWebhookType::Async(AsyncWebhookEventType::CategoryUpdated),
|
|
|
|
)
|
|
|
|
}
|
|
|
|
ItemType::Page => {
|
|
|
|
let mut data: PageCreated = self.into();
|
|
|
|
data.page = data.page.map(|mut p| {
|
|
|
|
p.slug = p.slug.clone() + slug_postfix;
|
|
|
|
p
|
|
|
|
});
|
|
|
|
(
|
|
|
|
serde_json::to_string_pretty(&data).unwrap(),
|
|
|
|
EitherWebhookType::Async(AsyncWebhookEventType::PageUpdated),
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
ItemType::Collection => {
|
|
|
|
let mut data: CollectionCreated = self.into();
|
|
|
|
data.collection = data.collection.map(|mut p| {
|
|
|
|
p.slug = p.slug.clone() + slug_postfix;
|
|
|
|
p
|
|
|
|
});
|
|
|
|
(
|
|
|
|
serde_json::to_string_pretty(&data).unwrap(),
|
|
|
|
EitherWebhookType::Async(AsyncWebhookEventType::CollectionUpdated),
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl From<Url> for ProductCreated {
|
|
|
|
fn from(value: Url) -> Self {
|
|
|
|
Self {
|
|
|
|
product: Some(Product {
|
|
|
|
slug: value.data.slug,
|
|
|
|
id: cynic::Id::new(value.data.id),
|
|
|
|
category: value.related.map(|c| Category {
|
|
|
|
slug: c.slug,
|
|
|
|
id: cynic::Id::new(c.id),
|
|
|
|
}),
|
|
|
|
}),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl From<Url> for CategoryCreated {
|
|
|
|
fn from(value: Url) -> Self {
|
|
|
|
Self {
|
|
|
|
category: Some(Category2 {
|
|
|
|
slug: value.data.slug,
|
|
|
|
id: cynic::Id::new(value.data.id),
|
|
|
|
}),
|
|
|
|
}
|
|
|
|
}
|
2024-07-10 21:51:59 +00:00
|
|
|
}
|
|
|
|
|
2024-07-16 15:46:00 +00:00
|
|
|
impl From<Url> for CollectionCreated {
|
|
|
|
fn from(value: Url) -> Self {
|
|
|
|
Self {
|
|
|
|
collection: Some(Collection {
|
|
|
|
slug: value.data.slug,
|
|
|
|
id: cynic::Id::new(value.data.id),
|
|
|
|
}),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl From<Url> for PageCreated {
|
|
|
|
fn from(value: Url) -> Self {
|
|
|
|
Self {
|
|
|
|
page: Some(Page {
|
|
|
|
slug: value.data.slug,
|
|
|
|
id: cynic::Id::new(value.data.id),
|
|
|
|
}),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl From<Url> for ProductDeleted {
|
|
|
|
fn from(value: Url) -> Self {
|
|
|
|
Self {
|
|
|
|
product: Some(Product {
|
|
|
|
slug: value.data.slug,
|
|
|
|
id: cynic::Id::new(value.data.id),
|
|
|
|
category: value.related.map(|c| Category {
|
|
|
|
slug: c.slug,
|
|
|
|
id: cynic::Id::new(c.id),
|
|
|
|
}),
|
|
|
|
}),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl From<Url> for CategoryDeleted {
|
|
|
|
fn from(value: Url) -> Self {
|
|
|
|
Self {
|
|
|
|
category: Some(Category2 {
|
|
|
|
slug: value.data.slug,
|
|
|
|
id: cynic::Id::new(value.data.id),
|
|
|
|
}),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl From<Url> for CollectionDeleted {
|
|
|
|
fn from(value: Url) -> Self {
|
|
|
|
Self {
|
|
|
|
collection: Some(Collection {
|
|
|
|
slug: value.data.slug,
|
|
|
|
id: cynic::Id::new(value.data.id),
|
|
|
|
}),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl From<Url> for PageDeleted {
|
|
|
|
fn from(value: Url) -> Self {
|
|
|
|
Self {
|
|
|
|
page: Some(Page {
|
|
|
|
slug: value.data.slug,
|
|
|
|
id: cynic::Id::new(value.data.id),
|
|
|
|
}),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2024-07-10 21:51:59 +00:00
|
|
|
#[derive(thiserror::Error, Debug)]
|
|
|
|
pub enum NewUrlError {
|
|
|
|
#[error("Some property inside passed data for new url was None, but should've been Some")]
|
|
|
|
MissingData,
|
|
|
|
#[error("Bad templates or wrong context data to fill out the template")]
|
|
|
|
BadTemplating(#[from] tinytemplate::error::Error),
|
2024-07-03 14:07:04 +00:00
|
|
|
}
|