clippy refactor, payment gateway payment flow revertion

This commit is contained in:
Djkáťo 2024-04-03 19:47:23 +02:00
parent 795793ca84
commit df27d18043
24 changed files with 413 additions and 444 deletions

7
.env
View file

@ -2,6 +2,7 @@
REQUIRED_SALEOR_VERSION="^3.13" REQUIRED_SALEOR_VERSION="^3.13"
# only sets port, the host is always 0.0.0.0 (listens to everything). Set this to docker-compose service name # only sets port, the host is always 0.0.0.0 (listens to everything). Set this to docker-compose service name
APP_API_BASE_URL="http://10.100.110.234:3000" APP_API_BASE_URL="http://10.100.110.234:3000"
APP_IFRAME_BASE_URL="http://app-name.site.com"
APL="Redis" APL="Redis"
APL_URL="redis://localhost:6380/2" APL_URL="redis://localhost:6380/2"
LOG_LEVEL="DEBUG" LOG_LEVEL="DEBUG"
@ -20,9 +21,9 @@ SITEMAP_PAGES_TEMPLATE="https://example.com/{page.slug}"
SITEMAP_INDEX_HOSTNAME="https://example.com" SITEMAP_INDEX_HOSTNAME="https://example.com"
## THESE VARIABLES ARE FOR SIMPLE-PAYMENT-GATEWAY APP ## THESE VARIABLES ARE FOR SIMPLE-PAYMENT-GATEWAY APP
#To see all possible options, check simple-payment-gateway/src/app:GatewayTypes #To see all possible options, check simple-payment-gateway/src/app:PaymentMethods
ACTIVE_GATEWAYS="cod,cash,transfer" ACTIVE_PAYMENT_METHODS="cod,cash,transfer"
# only SK,EN available :). Determines what language the gateway names will be in storefront # only SK,EN available :). Determines what language the gateway names will be in storefront
LOCALE="SK" LOCALE="Sk"
# uses https://crates.io/crates/iso_currency # uses https://crates.io/crates/iso_currency
CURRENCIES="EUR" CURRENCIES="EUR"

View file

@ -2,6 +2,7 @@
REQUIRED_SALEOR_VERSION="^3.13" REQUIRED_SALEOR_VERSION="^3.13"
# only sets port, the host is always 0.0.0.0 (listens to everything). Set this to docker-compose service name # only sets port, the host is always 0.0.0.0 (listens to everything). Set this to docker-compose service name
APP_API_BASE_URL="http://0.0.0.0:3000" APP_API_BASE_URL="http://0.0.0.0:3000"
APP_IFRAME_BASE_URL="http://app-name.site.com"
APL="Redis" APL="Redis"
APL_URL="redis://localhost:6379/1" APL_URL="redis://localhost:6379/1"
LOG_LEVEL="DEBUG" LOG_LEVEL="DEBUG"
@ -22,7 +23,7 @@ SITEMAP_INDEX_HOSTNAME="https://example.com"
## THESE VARIABLES ARE FOR SIMPLE-PAYMENT-GATEWAY APP ## THESE VARIABLES ARE FOR SIMPLE-PAYMENT-GATEWAY APP
#To see all possible options, check simple-payment-gateway/src/app:GatewayTypes #To see all possible options, check simple-payment-gateway/src/app:GatewayTypes
ACTIVE_GATEWAYS="cod,cash,transfer" ACTIVE_GATEWAYS="cod,cash,transfer"
# only SK,EN available :). Determines what language the gateway names will be in storefront # only Sk,En available :). Determines what language the gateway names will be in storefront
LOCALE="SK" LOCALE="Sk"
# uses https://crates.io/crates/iso_currency # uses https://crates.io/crates/iso_currency
CURRENCIES="EUR" CURRENCIES="EUR"

View file

@ -17,8 +17,11 @@ RUN cargo chef cook --release --recipe-path=recipe.json
COPY . . COPY . .
RUN cargo build --release RUN cargo build --release
FROM debian:bookworm-slim as chef-sitemap-generator FROM debian:bookworm-slim as chef-sitemap-generator
COPY --from=builder /apps/target/release/sitemap-generator /sitemap-generator WORKDIR /app
COPY --from=builder /apps/target/release/sitemap-generator .
COPY ./sitemap-generator/public ./public
RUN apt-get update -y && \ RUN apt-get update -y && \
apt-get install -y pkg-config libssl-dev curl apt-get install -y pkg-config libssl-dev curl
RUN mkdir /sitemaps RUN mkdir /sitemaps
@ -31,8 +34,11 @@ LABEL org.opencontainers.image.title="djkato/saleor-sitemap-generator"\
org.opencontainers.image.authors="Djkáťo <djkatovfx@gmail.com>"\ org.opencontainers.image.authors="Djkáťo <djkatovfx@gmail.com>"\
org.opencontainers.image.licenses="PolyForm-Noncommercial-1.0.0" org.opencontainers.image.licenses="PolyForm-Noncommercial-1.0.0"
FROM debian:bookworm-slim as chef-simple-payment-gateway FROM debian:bookworm-slim as chef-simple-payment-gateway
COPY --from=builder /apps/target/release/simple-payment-gateway /simple-payment-gateway WORKDIR /app
COPY --from=builder /apps/target/release/simple-payment-gateway .
COPY ./simple-payment-gateway/public ./public
RUN apt-get update -y && \ RUN apt-get update -y && \
apt-get install -y pkg-config libssl-dev curl apt-get install -y pkg-config libssl-dev curl
CMD [ "./simple-payment-gateway" ] CMD [ "./simple-payment-gateway" ]

View file

@ -79,6 +79,7 @@ and set all necessary env variables in `app-simple-gateway.env` according to the
To use, you need to have [Rust environment prepared](https://rustup.rs/). To use, you need to have [Rust environment prepared](https://rustup.rs/).
Every folder represents a different workspace. To add a new lib, do `cargo new <project-name> --lib` or `cargo new <project-name>` for binary apps. It should appear as a new member under root `Cargo.toml` Every folder represents a different workspace. To add a new lib, do `cargo new <project-name> --lib` or `cargo new <project-name>` for binary apps. It should appear as a new member under root `Cargo.toml`
To run apps propery, use `cargo run -c <crate name>`
# Unofficial Saleor App SDK # Unofficial Saleor App SDK

View file

@ -33,22 +33,18 @@ where
} }
} }
pub fn trace_to_std(config: &Config) { pub fn trace_to_std(config: &Config) -> anyhow::Result<()> {
let filter = EnvFilter::builder() let filter = EnvFilter::builder()
.with_default_directive(LevelFilter::DEBUG.into()) .with_default_directive(LevelFilter::DEBUG.into())
.from_env() .from_env()?
.unwrap() .add_directive(format!("{}={}", env!("CARGO_PKG_NAME"), config.log_level).parse()?);
.add_directive(
format!("{}={}", env!("CARGO_PKG_NAME"), config.log_level)
.parse()
.unwrap(),
);
tracing_subscriber::fmt() tracing_subscriber::fmt()
.with_max_level(config.log_level) .with_max_level(config.log_level)
.with_env_filter(filter) .with_env_filter(filter)
.with_target(true) .with_target(true)
.compact() .compact()
.init(); .init();
Ok(())
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]

View file

@ -1,3 +1,11 @@
#![allow(
non_upper_case_globals,
clippy::large_enum_variant,
clippy::upper_case_acronyms
)]
#![feature(let_chains)]
#![deny(clippy::unwrap_used, clippy::expect_used)]
mod app; mod app;
mod queries; mod queries;
mod routes; mod routes;
@ -5,8 +13,8 @@ mod routes;
use saleor_app_sdk::{ use saleor_app_sdk::{
cargo_info, cargo_info,
config::Config, config::Config,
manifest::{AppManifest, AppPermission}, manifest::{AppManifestBuilder, AppPermission},
webhooks::{AsyncWebhookEventType, WebhookManifest}, webhooks::{AsyncWebhookEventType, WebhookManifestBuilder},
SaleorApp, SaleorApp,
}; };
use std::sync::Arc; use std::sync::Arc;
@ -20,13 +28,13 @@ use crate::{
#[tokio::main] #[tokio::main]
async fn main() -> anyhow::Result<()> { async fn main() -> anyhow::Result<()> {
let config = Config::load()?; let config = Config::load()?;
trace_to_std(&config); trace_to_std(&config)?;
let saleor_app = SaleorApp::new(&config)?; let saleor_app = SaleorApp::new(&config)?;
let app_manifest = AppManifest::new(&config, cargo_info!()) let app_manifest = AppManifestBuilder::new(&config, cargo_info!())
.add_webhook( .add_webhook(
WebhookManifest::new(&config) WebhookManifestBuilder::new(&config)
.set_query( .set_query(
r#" r#"
subscription QueryProductsChanged { subscription QueryProductsChanged {
@ -85,7 +93,7 @@ async fn main() -> anyhow::Result<()> {
.unwrap_or(&"3000"), .unwrap_or(&"3000"),
) )
.await?; .await?;
tracing::debug!("listening on {}", listener.local_addr().unwrap()); tracing::debug!("listening on {}", listener.local_addr()?);
match axum::serve(listener, app).await { match axum::serve(listener, app).await {
Ok(o) => Ok(o), Ok(o) => Ok(o),
Err(e) => anyhow::bail!(e), Err(e) => anyhow::bail!(e),

View file

@ -22,7 +22,7 @@ pub fn create_routes(state: AppState) -> Router {
(StatusCode::NOT_FOUND, "Not found") (StatusCode::NOT_FOUND, "Not found")
} }
let service = handle_404.into_service(); let service = handle_404.into_service();
let serve_dir = ServeDir::new("saleor-app-template/public").not_found_service(service); let serve_dir = ServeDir::new("./public").not_found_service(service);
Router::new() Router::new()
.layer(middleware::from_fn(webhook_signature_verifier)) .layer(middleware::from_fn(webhook_signature_verifier))

View file

@ -37,16 +37,15 @@ pub async fn webhooks(
.get(SALEOR_API_URL_HEADER) .get(SALEOR_API_URL_HEADER)
.context("missing saleor api url header")?; .context("missing saleor api url header")?;
let event_type = get_webhook_event_type(&headers)?; let event_type = get_webhook_event_type(&headers)?;
match event_type { if let EitherWebhookType::Async(a) = event_type {
EitherWebhookType::Async(a) => match a { match a {
AsyncWebhookEventType::ProductUpdated AsyncWebhookEventType::ProductUpdated
| AsyncWebhookEventType::ProductCreated | AsyncWebhookEventType::ProductCreated
| AsyncWebhookEventType::ProductDeleted => { | AsyncWebhookEventType::ProductDeleted => {
update_product(product, url.to_str()?, state).await? update_product(product, url.to_str()?, state).await?
} }
_ => (), _ => (),
}, }
_ => (),
} }
info!("got webhooks!"); info!("got webhooks!");

View file

@ -1,8 +1,7 @@
## COMMON VARIABLES FOR ALL APPS ## COMMON VARIABLES FOR ALL APPS
REQUIRED_SALEOR_VERSION="^3.13" REQUIRED_SALEOR_VERSION="^3.13"
APP_API_BASE_URL="http://0.0.0.0:3001" APP_API_BASE_URL="http://app-payment-gateway:3000"
MANIFEST_URL="http://0.0.0.0:3001" APP_IFRAME_BASE_URL="http://app-payment-gateway.site.com"
CONFIG_URL="http://0.0.0.0:3001"
APL="Redis" APL="Redis"
APL_URL="redis://redis-apl:6379/1" APL_URL="redis://redis-apl:6379/1"
LOG_LEVEL="DEBUG" LOG_LEVEL="DEBUG"
@ -23,5 +22,7 @@ SITEMAP_INDEX_HOSTNAME="https://example.com"
## THESE VARIABLES ARE FOR SIMPLE-PAYMENT-GATEWAY APP ## THESE VARIABLES ARE FOR SIMPLE-PAYMENT-GATEWAY APP
#To see all possible options, check simple-payment-gateway/src/app:GatewayTypes #To see all possible options, check simple-payment-gateway/src/app:GatewayTypes
ACTIVE_GATEWAYS="cod,cash,transfer" ACTIVE_GATEWAYS="cod,cash,transfer"
# only SK,EN available :). Determines what language the gateway names will be in storefront # only Sk,En available :). Determines what language the gateway names will be in storefront
LOCALE="SK" LOCALE="Sk"
# uses https://crates.io/crates/iso_currency
CURRENCIES="EUR"

View file

@ -1,6 +1,7 @@
## COMMON VARIABLES FOR ALL APPS ## COMMON VARIABLES FOR ALL APPS
REQUIRED_SALEOR_VERSION="^3.13" REQUIRED_SALEOR_VERSION="^3.13"
APP_API_BASE_URL="http://0.0.0.0:3002" APP_API_BASE_URL="http://0.0.0.0:3000"
APP_IFRAME_BASE_URL="http://app-payment-gateway.site.com"
APL="Redis" APL="Redis"
APL_URL="redis://redis-apl:6379/1" APL_URL="redis://redis-apl:6379/1"
LOG_LEVEL="DEBUG" LOG_LEVEL="DEBUG"

View file

@ -24,6 +24,7 @@ pub struct Config {
#[serde(default = "version_default")] #[serde(default = "version_default")]
pub required_saleor_version: String, pub required_saleor_version: String,
pub app_api_base_url: String, pub app_api_base_url: String,
pub app_iframe_base_url: String,
pub apl: AplType, pub apl: AplType,
pub apl_url: String, pub apl_url: String,
#[serde(with = "LocalTracingLevel")] #[serde(with = "LocalTracingLevel")]

View file

@ -214,7 +214,7 @@ macro_rules! cargo_info {
} }
pub use cargo_info; pub use cargo_info;
impl AppManifest { impl AppManifestBuilder {
/** /**
* Builder for AppManifest * Builder for AppManifest
* *
@ -227,7 +227,7 @@ impl AppManifest {
* To set webhooks and permissions use the add_webhook() and add_permissions() * To set webhooks and permissions use the add_webhook() and add_permissions()
* *
*/ */
pub fn new(config: &Config, info: CargoInfo) -> AppManifestBuilder { pub fn new(config: &Config, info: CargoInfo) -> Self {
AppManifestBuilder { AppManifestBuilder {
manifest: AppManifest { manifest: AppManifest {
id: info.name.clone(), id: info.name.clone(),

View file

@ -269,15 +269,15 @@ impl WebhookManifestBuilder {
} }
} }
impl WebhookManifest { impl WebhookManifestBuilder {
/** /**
* Creates defaults of name(<cargo_app_name> webhook) and target url(/api/webhooks) from config and env. * Creates defaults of name(<cargo_app_name> webhook) and target url(/api/webhooks) from config and env.
*/ */
pub fn new(config: &Config) -> WebhookManifestBuilder { pub fn new(config: &Config) -> Self {
WebhookManifestBuilder { WebhookManifestBuilder {
webhook_manifest: WebhookManifest { webhook_manifest: WebhookManifest {
target_url: format!("{}/api/webhooks", config.app_api_base_url), target_url: format!("{}/api/webhooks", config.app_api_base_url),
name: env!("CARGO_PKG_NAME").to_owned() + " webhook", name: "webhook".to_owned(),
is_active: Some(true), is_active: Some(true),
..Default::default() ..Default::default()
}, },

View file

@ -4,6 +4,7 @@ use crate::headers::SALEOR_EVENT_HEADER;
use super::{AsyncWebhookEventType, SyncWebhookEventType}; use super::{AsyncWebhookEventType, SyncWebhookEventType};
#[derive(Debug)]
pub enum EitherWebhookType { pub enum EitherWebhookType {
Sync(SyncWebhookEventType), Sync(SyncWebhookEventType),
Async(AsyncWebhookEventType), Async(AsyncWebhookEventType),

View file

@ -5,14 +5,11 @@ use axum::{
use enum_iterator::{all, Sequence}; use enum_iterator::{all, Sequence};
use iso_currency::Currency; use iso_currency::Currency;
use std::{str::FromStr, sync::Arc}; use std::{str::FromStr, sync::Arc};
use tracing::level_filters::LevelFilter; use tracing::{debug, level_filters::LevelFilter};
use tracing_subscriber::EnvFilter; use tracing_subscriber::EnvFilter;
use saleor_app_sdk::{ use saleor_app_sdk::{config::Config, locales::LocaleCode, manifest::AppManifest, SaleorApp};
config::Config, locales::LocaleCode, manifest::AppManifest, use serde::{Deserialize, Serialize};
webhooks::sync_response::PaymentGateway, SaleorApp,
};
use serde::Serialize;
// Make our own error that wraps `anyhow::Error`. // Make our own error that wraps `anyhow::Error`.
pub struct AppError(anyhow::Error); pub struct AppError(anyhow::Error);
@ -38,27 +35,23 @@ where
} }
} }
pub fn trace_to_std(config: &Config) { pub fn trace_to_std(config: &Config) -> anyhow::Result<()> {
let filter = EnvFilter::builder() let filter = EnvFilter::builder()
.with_default_directive(LevelFilter::DEBUG.into()) .with_default_directive(LevelFilter::DEBUG.into())
.from_env() .from_env()?
.unwrap() .add_directive(format!("{}={}", env!("CARGO_PKG_NAME"), config.log_level).parse()?);
.add_directive(
format!("{}={}", env!("CARGO_PKG_NAME"), config.log_level)
.parse()
.unwrap(),
);
tracing_subscriber::fmt() tracing_subscriber::fmt()
.with_max_level(config.log_level) .with_max_level(config.log_level)
.with_env_filter(filter) .with_env_filter(filter)
.with_target(true) .with_target(true)
.compact() .compact()
.init(); .init();
Ok(())
} }
#[derive(Debug, Clone, Sequence, Serialize)] #[derive(Debug, Clone, Sequence, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")] #[serde(rename_all = "lowercase")]
pub enum GatewayType { pub enum PaymentMethodType {
Accreditation, Accreditation,
Cash, Cash,
/** /**
@ -75,16 +68,16 @@ pub struct AppState {
pub saleor_app: Arc<tokio::sync::Mutex<SaleorApp>>, pub saleor_app: Arc<tokio::sync::Mutex<SaleorApp>>,
pub config: Config, pub config: Config,
pub manifest: AppManifest, pub manifest: AppManifest,
pub active_gateways: Vec<ActiveGateway>, pub active_payment_methods: Vec<ActivePaymentMethod>,
} }
pub fn get_active_gateways_from_env() -> anyhow::Result<Vec<ActiveGateway>> { pub fn get_active_payment_methods_from_env() -> anyhow::Result<Vec<ActivePaymentMethod>> {
_ = dotenvy::dotenv(); _ = dotenvy::dotenv();
//eg: "accreditation,cod,other,transfer" //eg: "accreditation,cod,other,transfer"
let env_types = std::env::var("ACTIVE_GATEWAYS")?; let env_methods = std::env::var("ACTIVE_PAYMENT_METHODS")?;
let locale = std::env::var("LOCALE")?; let locale = std::env::var("LOCALE")?;
let locale = LocaleCode::from_str(&locale)?;
let currencies = std::env::var("CURRENCIES")?; let currencies = std::env::var("CURRENCIES")?;
let locale = LocaleCode::from_str(&locale)?;
let currencies = currencies.split(',').collect::<Vec<_>>(); let currencies = currencies.split(',').collect::<Vec<_>>();
let currencies = currencies let currencies = currencies
.iter() .iter()
@ -92,44 +85,52 @@ pub fn get_active_gateways_from_env() -> anyhow::Result<Vec<ActiveGateway>> {
.collect::<Result<Vec<_>, _>>() .collect::<Result<Vec<_>, _>>()
.map_err(|e| anyhow::anyhow!(format!("{:?}", e)))?; .map_err(|e| anyhow::anyhow!(format!("{:?}", e)))?;
let str_types: Vec<_> = env_types.split(',').collect(); let str_types: Vec<_> = env_methods.split(',').collect();
let gateway_types = str_types let payment_methods = str_types
.iter() .iter()
.zip(all::<GatewayType>()) .flat_map(|s| all::<PaymentMethodType>().map(move |g| (s, g)))
.filter_map(|(s, g)| match format!("{:?}", g).to_lowercase() == *s { .filter_map(|(s, g)| match format!("{:?}", g).to_lowercase() == *s {
true => Some(g), true => Some(g),
false => None, false => None,
}) })
.map(|g| ActiveGateway { .map(|g| ActivePaymentMethod {
gateway_type: g.clone(), typ: g.clone(),
gateway: PaymentGateway { name: match (g, &locale) {
currencies: currencies.clone(), (PaymentMethodType::COD, LocaleCode::Sk) => "Dobierka".to_owned(),
id: format!("{:?}", &g).to_lowercase(), (PaymentMethodType::Cash, LocaleCode::Sk) => "Hotovosť".to_owned(),
config: vec![], (PaymentMethodType::Transfer, LocaleCode::Sk) => "Bankový prevod".to_owned(),
name: match (g, &locale) { (PaymentMethodType::Inkaso, LocaleCode::Sk) => "Inkaso".to_owned(),
(GatewayType::COD, LocaleCode::Sk) => "Dobierka".to_owned(), (PaymentMethodType::Accreditation, LocaleCode::Sk) => "Vzajomný zápočet".to_owned(),
(GatewayType::Cash, LocaleCode::Sk) => "Hotovosť".to_owned(), (PaymentMethodType::Other, LocaleCode::Sk) => "Iné".to_owned(),
(GatewayType::Transfer, LocaleCode::Sk) => "Bankový prevod".to_owned(), (PaymentMethodType::COD, LocaleCode::En) => "Cash on delivery".to_owned(),
(GatewayType::Inkaso, LocaleCode::Sk) => "Inkaso".to_owned(), (PaymentMethodType::Cash, LocaleCode::En) => "Cash".to_owned(),
(GatewayType::Accreditation, LocaleCode::Sk) => "Vzajomný zápočet".to_owned(), (PaymentMethodType::Transfer, LocaleCode::En) => "Bank transfer".to_owned(),
(GatewayType::Other, LocaleCode::Sk) => "Iné".to_owned(), (PaymentMethodType::Inkaso, LocaleCode::En) => "Encashment".to_owned(),
(GatewayType::COD, LocaleCode::En) => "Cash on delivery".to_owned(), (PaymentMethodType::Accreditation, LocaleCode::En) => "Mutual credit".to_owned(),
(GatewayType::Cash, LocaleCode::En) => "Cash".to_owned(), (PaymentMethodType::Other, LocaleCode::En) => "Other".to_owned(),
(GatewayType::Transfer, LocaleCode::En) => "Bank transfer".to_owned(), (g, l) => unimplemented!("Gateway {:?} in locale {:?} not implemented", g, l),
(GatewayType::Inkaso, LocaleCode::En) => "Encashment".to_owned(),
(GatewayType::Accreditation, LocaleCode::En) => "Mutual credit".to_owned(),
(GatewayType::Other, LocaleCode::En) => "Other".to_owned(),
(g, l) => unimplemented!("Gateway {:?} in locale {:?} not implemented", g, l),
},
}, },
}) })
.collect::<Vec<_>>(); .collect::<Vec<_>>();
debug!(
Ok(gateway_types) "active gateway types:{:?}\ncurrencies:{:?}\nlocale:{:?}",
&payment_methods, &currencies, &locale
);
Ok(payment_methods)
} }
#[derive(Debug, Clone, Serialize)] #[derive(Debug, Clone, Serialize)]
pub struct ActiveGateway { pub struct ActivePaymentMethod {
pub gateway_type: GatewayType, pub typ: PaymentMethodType,
pub gateway: PaymentGateway, pub name: String,
}
#[derive(Serialize)]
pub struct PaymentGatewayInitializeSessionData {
pub payment_methods: Vec<ActivePaymentMethod>,
}
#[derive(Deserialize, Serialize)]
pub struct TransactionInitializeSessionData {
pub payment_method: PaymentMethodType,
} }

View file

@ -1,4 +1,8 @@
#![allow(non_upper_case_globals)] #![allow(
non_upper_case_globals,
clippy::large_enum_variant,
clippy::upper_case_acronyms
)]
#![feature(let_chains)] #![feature(let_chains)]
#![deny(clippy::unwrap_used, clippy::expect_used)] #![deny(clippy::unwrap_used, clippy::expect_used)]
mod app; mod app;
@ -8,20 +12,19 @@ mod routes;
use saleor_app_sdk::{ use saleor_app_sdk::{
cargo_info, cargo_info,
config::Config, config::Config,
manifest::{AppManifest, AppPermission}, manifest::{AppManifestBuilder, AppPermission},
webhooks::{SyncWebhookEventType, WebhookManifest}, webhooks::{SyncWebhookEventType, WebhookManifestBuilder},
SaleorApp, SaleorApp,
}; };
use std::sync::Arc; use std::sync::Arc;
use tokio::sync::Mutex; use tokio::sync::Mutex;
use crate::{ use crate::{
app::{get_active_gateways_from_env, trace_to_std, AppState}, app::{get_active_payment_methods_from_env, trace_to_std, AppState},
queries::event_transactions::{ queries::event_transactions::{
sub_list_payment_gateways, sub_payment_gateway_initialize_session, sub_payment_gateway_initialize_session, sub_transaction_cancelation_requested,
sub_transaction_cancelation_requested, sub_transaction_charge_requested, sub_transaction_charge_requested, sub_transaction_initialize_session,
sub_transaction_initialize_session, sub_transaction_process_session, sub_transaction_process_session, sub_transaction_refund_requested,
sub_transaction_refund_requested,
}, },
routes::create_routes, routes::create_routes,
}; };
@ -29,53 +32,53 @@ use crate::{
#[tokio::main] #[tokio::main]
async fn main() -> anyhow::Result<()> { async fn main() -> anyhow::Result<()> {
let config = Config::load()?; let config = Config::load()?;
trace_to_std(&config); trace_to_std(&config)?;
let saleor_app = SaleorApp::new(&config)?; let saleor_app = SaleorApp::new(&config)?;
let app_manifest = AppManifest::new(&config, cargo_info!()) let app_manifest = AppManifestBuilder::new(&config, cargo_info!())
.add_webhook( .add_webhook(
WebhookManifest::new(&config) WebhookManifestBuilder::new(&config)
.set_query(sub_transaction_process_session) .set_query(sub_transaction_process_session)
.add_sync_event(SyncWebhookEventType::TransactionProcessSession) .add_sync_event(SyncWebhookEventType::TransactionProcessSession)
.build(), .build(),
) )
.add_webhook( .add_webhook(
WebhookManifest::new(&config) WebhookManifestBuilder::new(&config)
.set_query(sub_transaction_charge_requested) .set_query(sub_transaction_charge_requested)
.add_sync_event(SyncWebhookEventType::TransactionChargeRequested) .add_sync_event(SyncWebhookEventType::TransactionChargeRequested)
.build(), .build(),
) )
.add_webhook( .add_webhook(
WebhookManifest::new(&config) WebhookManifestBuilder::new(&config)
.set_query(sub_transaction_refund_requested) .set_query(sub_transaction_refund_requested)
.add_sync_event(SyncWebhookEventType::TransactionRefundRequested) .add_sync_event(SyncWebhookEventType::TransactionRefundRequested)
.build(), .build(),
) )
.add_webhook( .add_webhook(
WebhookManifest::new(&config) WebhookManifestBuilder::new(&config)
.set_query(sub_transaction_initialize_session) .set_query(sub_transaction_initialize_session)
.add_sync_event(SyncWebhookEventType::TransactionInitializeSession) .add_sync_event(SyncWebhookEventType::TransactionInitializeSession)
.build(), .build(),
) )
.add_webhook( .add_webhook(
WebhookManifest::new(&config) WebhookManifestBuilder::new(&config)
.set_query(sub_payment_gateway_initialize_session) .set_query(sub_payment_gateway_initialize_session)
.add_sync_event(SyncWebhookEventType::PaymentGatewayInitializeSession) .add_sync_event(SyncWebhookEventType::PaymentGatewayInitializeSession)
.build(), .build(),
) )
.add_webhook( .add_webhook(
WebhookManifest::new(&config) WebhookManifestBuilder::new(&config)
.set_query(sub_transaction_cancelation_requested) .set_query(sub_transaction_cancelation_requested)
.add_sync_event(SyncWebhookEventType::TransactionCancelationRequested) .add_sync_event(SyncWebhookEventType::TransactionCancelationRequested)
.build(), .build(),
) )
.add_webhook( // .add_webhook(
WebhookManifest::new(&config) // WebhookManifestBuilder::new(&config)
.set_query(sub_list_payment_gateways) // .set_query(sub_list_payment_gateways)
.add_sync_event(SyncWebhookEventType::PaymentListGateways) // .add_sync_event(SyncWebhookEventType::PaymentListGateways)
.build(), // .build(),
) // )
.add_permissions(vec![ .add_permissions(vec![
AppPermission::HandlePayments, AppPermission::HandlePayments,
AppPermission::ManageOrders, AppPermission::ManageOrders,
@ -85,7 +88,7 @@ async fn main() -> anyhow::Result<()> {
.build(); .build();
let app_state = AppState { let app_state = AppState {
active_gateways: get_active_gateways_from_env()?, active_payment_methods: get_active_payment_methods_from_env()?,
manifest: app_manifest, manifest: app_manifest,
config: config.clone(), config: config.clone(),
saleor_app: Arc::new(Mutex::new(saleor_app)), saleor_app: Arc::new(Mutex::new(saleor_app)),
@ -102,7 +105,7 @@ async fn main() -> anyhow::Result<()> {
.unwrap_or(&"3000"), .unwrap_or(&"3000"),
) )
.await?; .await?;
tracing::debug!("listening on {}", listener.local_addr().unwrap()); tracing::debug!("listening on {}", listener.local_addr()?);
match axum::serve(listener, app).await { match axum::serve(listener, app).await {
Ok(o) => Ok(o), Ok(o) => Ok(o),
Err(e) => anyhow::bail!(e), Err(e) => anyhow::bail!(e),

View file

@ -55,17 +55,17 @@ fragment OrderDetails on Order {
} }
"#; "#;
pub const sub_list_payment_gateways: &str = r#" // pub const sub_list_payment_gateways: &str = r#"
subscription ListPaymentGateways { // subscription ListPaymentGateways {
event { // event {
... on PaymentListGateways { // ... on PaymentListGateways {
checkout { // checkout {
id // id
} // }
} // }
} // }
} // }
"#; // "#;
pub const sub_payment_gateway_initialize_session: &str = concatcp!( pub const sub_payment_gateway_initialize_session: &str = concatcp!(
r#" r#"

View file

@ -22,7 +22,7 @@ pub fn create_routes(state: AppState) -> Router {
(StatusCode::NOT_FOUND, "Not found") (StatusCode::NOT_FOUND, "Not found")
} }
let service = handle_404.into_service(); let service = handle_404.into_service();
let serve_dir = ServeDir::new("simple-payment-gateway/public").not_found_service(service); let serve_dir = ServeDir::new("./public").not_found_service(service);
Router::new() Router::new()
.layer(middleware::from_fn(webhook_signature_verifier)) .layer(middleware::from_fn(webhook_signature_verifier))

View file

@ -7,23 +7,23 @@ use saleor_app_sdk::{
webhooks::{ webhooks::{
sync_response::{ sync_response::{
CancelationRequestedResult, ChargeRequestedResult, CancelationRequestedResult, ChargeRequestedResult,
PaymentGatewayInitializeSessionResponse, PaymentListGatewaysResponse, PaymentGatewayInitializeSessionResponse, RefundRequestedResult,
RefundRequestedResult, TransactionCancelationRequestedResponse, TransactionCancelationRequestedResponse, TransactionChargeRequestedResponse,
TransactionChargeRequestedResponse, TransactionInitializeSessionResponse, TransactionInitializeSessionResponse, TransactionProcessSessionResponse,
TransactionProcessSessionResponse, TransactionRefundRequestedResponse, TransactionRefundRequestedResponse, TransactionSessionResult,
TransactionSessionResult,
}, },
utils::{get_webhook_event_type, EitherWebhookType}, utils::{get_webhook_event_type, EitherWebhookType},
SyncWebhookEventType, SyncWebhookEventType,
}, },
}; };
use serde::Serialize;
use serde_json::Value; use serde_json::Value;
use std::str::FromStr; use std::str::FromStr;
use tracing::{debug, error, info}; use tracing::{debug, error, info};
use crate::{ use crate::{
app::{ActiveGateway, AppError, AppState, GatewayType}, app::{
AppError, AppState, PaymentGatewayInitializeSessionData, TransactionInitializeSessionData,
},
queries::{ queries::{
event_transactions::{ event_transactions::{
TransactionCancelationRequested2, TransactionChargeRequested2, TransactionCancelationRequested2, TransactionChargeRequested2,
@ -43,7 +43,6 @@ pub async fn webhooks(
) -> Result<Json<Value>, AppError> { ) -> Result<Json<Value>, AppError> {
debug!("/api/webhooks"); debug!("/api/webhooks");
debug!("req: {:?}", body); debug!("req: {:?}", body);
debug!("headers: {:?}", headers);
let saleor_api_url = headers let saleor_api_url = headers
.get(SALEOR_API_URL_HEADER) .get(SALEOR_API_URL_HEADER)
@ -51,57 +50,96 @@ pub async fn webhooks(
.to_str()? .to_str()?
.to_owned(); .to_owned();
let event_type = get_webhook_event_type(&headers)?; let event_type = get_webhook_event_type(&headers)?;
debug!("event: {:?}", event_type);
let res: Json<Value> = match event_type { let res: Json<Value> = match event_type {
EitherWebhookType::Sync(a) => match a { EitherWebhookType::Sync(a) => match a {
SyncWebhookEventType::PaymentListGateways => { // SyncWebhookEventType::PaymentListGateways => {
let gateways = state // let gateways = state
.active_gateways // .active_gateways
.iter() // .iter()
.cloned() // .cloned()
.map(|g| g.gateway) // .map(|g| g.gateway)
.collect::<Vec<_>>(); // .collect::<Vec<_>>();
Json::from(serde_json::to_value(PaymentListGatewaysResponse(gateways))?) // Json::from(serde_json::to_value(PaymentListGatewaysResponse(gateways))?)
} // }
SyncWebhookEventType::PaymentGatewayInitializeSession => {
SyncWebhookEventType::TransactionCancelationRequested => { let data = serde_json::to_value(PaymentGatewayInitializeSessionData {
let data = serde_json::from_str::<TransactionCancelationRequested2>(&body)?; payment_methods: state.active_payment_methods,
})?;
Json::from(serde_json::to_value( Json::from(serde_json::to_value(
TransactionCancelationRequestedResponse { PaymentGatewayInitializeSessionResponse::<Value> { data: Some(data) },
time: None,
psp_reference: "".to_owned(),
external_url: None,
message: None,
amount: data
.action
.amount
.and_then(|a| Decimal::from_str(&a.0).ok()),
result: Some(CancelationRequestedResult::CancelSuccess),
},
)?) )?)
} }
SyncWebhookEventType::PaymentGatewayInitializeSession => Json::from( SyncWebhookEventType::TransactionInitializeSession => {
serde_json::to_value(PaymentGatewayInitializeSessionResponse::<u8> { data: None })?, let session_data = serde_json::from_str::<TransactionInitializeSession2>(&body)?;
), let payment_method: TransactionInitializeSessionData = serde_json::from_str(
SyncWebhookEventType::TransactionProcessSession => { &session_data
let data = serde_json::from_str::<TransactionProcessSession2>(&body)?; .data
Json::from(serde_json::to_value(TransactionProcessSessionResponse::< .context("Missing data field on TransactionInitializeSession2 body")?
u8, .0,
> { )?;
data: None, debug!(
time: None, "Transaction session initialised with '{:?}' payment method.",
psp_reference: None, &payment_method.payment_method
external_url: None, );
message: None, let str_payment_method = serde_json::to_string(&payment_method)?;
amount: Decimal::from_str(&data.action.amount.0)?,
result: match data.action.action_type { let app = state.saleor_app.lock().await;
TransactionFlowStrategyEnum::Charge => { let auth_data = app.apl.get(&saleor_api_url).await?;
TransactionSessionResult::ChargeSuccess
} let operation = TransactionUpdate::build(TransactionUpdateVariables {
TransactionFlowStrategyEnum::Authorization => { id: &session_data.transaction.id,
TransactionSessionResult::AuthorizationSuccess transaction: Some(TransactionUpdateInput {
} message: Some(&str_payment_method),
..Default::default()
}),
});
let mut res = surf::post(&saleor_api_url)
.header("authorization-bearer", auth_data.token)
.run_graphql(operation)
.await;
let mut webhook_result = WebhookResult::Failiure;
if let Ok(r) = &mut res
&& let Some(data) = &mut r.data
&& let Some(q_res) = &mut data.transaction_update
{
if !q_res.errors.is_empty() {
q_res
.errors
.iter()
.for_each(|e| error!("failed update transaction, {:?}", e));
} else if q_res.transaction.is_some() {
webhook_result = WebhookResult::Success;
}
}
Json::from(serde_json::to_value(
TransactionInitializeSessionResponse::<u8> {
data: None,
time: None,
psp_reference: None,
external_url: None,
message: None,
amount: Decimal::from_str(&session_data.action.amount.0)?,
result: match (session_data.action.action_type, webhook_result) {
(TransactionFlowStrategyEnum::Charge, WebhookResult::Success) => {
TransactionSessionResult::ChargeSuccess
}
(
TransactionFlowStrategyEnum::Authorization,
WebhookResult::Success,
) => TransactionSessionResult::AuthorizationSuccess,
(TransactionFlowStrategyEnum::Charge, WebhookResult::Failiure) => {
TransactionSessionResult::ChargeFailiure
}
(
TransactionFlowStrategyEnum::Authorization,
WebhookResult::Failiure,
) => TransactionSessionResult::AuthorizationFailure,
},
}, },
})?) )?)
} }
SyncWebhookEventType::TransactionChargeRequested => { SyncWebhookEventType::TransactionChargeRequested => {
let data = serde_json::from_str::<TransactionChargeRequested2>(&body)?; let data = serde_json::from_str::<TransactionChargeRequested2>(&body)?;
@ -131,91 +169,54 @@ pub async fn webhooks(
result: Some(RefundRequestedResult::RefundSuccess), result: Some(RefundRequestedResult::RefundSuccess),
})?) })?)
} }
SyncWebhookEventType::TransactionInitializeSession => {
let data = serde_json::from_str::<TransactionInitializeSession2>(&body)?;
let app = state.saleor_app.lock().await;
let auth_data = app.apl.get(&saleor_api_url).await?;
let operation = TransactionUpdate::build(TransactionUpdateVariables {
id: &data.transaction.id,
transaction: Some(TransactionUpdateInput {
message: Some(""),
..Default::default()
}),
});
let mut res = surf::post(&saleor_api_url)
.header("authorization-bearer", auth_data.token)
.run_graphql(operation)
.await;
let mut webhook_result = WebhookResult::Failiure;
if let Ok(r) = &mut res
&& let Some(data) = &mut r.data
&& let Some(q_res) = &mut data.transaction_update
{
if !q_res.errors.is_empty() {
q_res
.errors
.iter()
.for_each(|e| error!("failed update transaction, {:?}", e));
} else if let Some(tr) = &mut q_res.transaction {
tr.message = serde_json::to_string(&PaymentMethod {
payment_method: GatewayType::COD,
})?;
webhook_result = WebhookResult::Success;
}
}
SyncWebhookEventType::TransactionCancelationRequested => {
let data = serde_json::from_str::<TransactionCancelationRequested2>(&body)?;
Json::from(serde_json::to_value( Json::from(serde_json::to_value(
TransactionInitializeSessionResponse::<u8> { TransactionCancelationRequestedResponse {
data: None,
time: None, time: None,
psp_reference: None, psp_reference: "".to_owned(),
external_url: None, external_url: None,
message: None, message: None,
amount: Decimal::from_str(&data.action.amount.0)?, amount: data
result: match (data.action.action_type, webhook_result) { .action
(TransactionFlowStrategyEnum::Charge, WebhookResult::Success) => { .amount
TransactionSessionResult::ChargeSuccess .and_then(|a| Decimal::from_str(&a.0).ok()),
} result: Some(CancelationRequestedResult::CancelSuccess),
(
TransactionFlowStrategyEnum::Authorization,
WebhookResult::Success,
) => TransactionSessionResult::AuthorizationSuccess,
(TransactionFlowStrategyEnum::Charge, WebhookResult::Failiure) => {
TransactionSessionResult::ChargeFailiure
}
(
TransactionFlowStrategyEnum::Authorization,
WebhookResult::Failiure,
) => TransactionSessionResult::AuthorizationFailure,
},
}, },
)?) )?)
} }
SyncWebhookEventType::TransactionProcessSession => {
let data = serde_json::from_str::<TransactionProcessSession2>(&body)?;
Json::from(serde_json::to_value(TransactionProcessSessionResponse::<
u8,
> {
data: None,
time: None,
psp_reference: None,
external_url: None,
message: None,
amount: Decimal::from_str(&data.action.amount.0)?,
result: match data.action.action_type {
TransactionFlowStrategyEnum::Charge => {
TransactionSessionResult::ChargeSuccess
}
TransactionFlowStrategyEnum::Authorization => {
TransactionSessionResult::AuthorizationSuccess
}
},
})?)
}
_ => Json::from(Value::from_str("")?), _ => Json::from(Value::from_str("")?),
}, },
_ => Json::from(Value::from_str("")?), _ => Json::from(Value::from_str("")?),
}; };
debug!("{:?}", res.to_string());
info!("got webhooks!"); info!("got webhooks!");
Ok(res) Ok(res)
} }
#[derive(Serialize, Clone, Debug)]
pub struct JsonResponse {
data: JsonResponseData,
}
#[derive(Serialize, Clone, Debug)]
pub struct JsonResponseData {
current_gateway: ActiveGateway,
}
enum WebhookResult { enum WebhookResult {
Success, Success,
Failiure, Failiure,
} }
#[derive(Serialize, Clone, Debug)]
struct PaymentMethod {
payment_method: GatewayType,
}

View file

@ -36,22 +36,18 @@ where
} }
} }
pub fn trace_to_std(config: &Config) { pub fn trace_to_std(config: &Config) -> anyhow::Result<()> {
let filter = EnvFilter::builder() let filter = EnvFilter::builder()
.with_default_directive(LevelFilter::DEBUG.into()) .with_default_directive(LevelFilter::DEBUG.into())
.from_env() .from_env()?
.unwrap() .add_directive(format!("{}={}", env!("CARGO_PKG_NAME"), config.log_level).parse()?);
.add_directive(
format!("{}={}", env!("CARGO_PKG_NAME"), config.log_level)
.parse()
.unwrap(),
);
tracing_subscriber::fmt() tracing_subscriber::fmt()
.with_max_level(config.log_level) .with_max_level(config.log_level)
.with_env_filter(filter) .with_env_filter(filter)
.with_target(true) .with_target(true)
.compact() .compact()
.init(); .init();
Ok(())
} }
/** /**

View file

@ -1,3 +1,8 @@
#![allow(
non_upper_case_globals,
clippy::large_enum_variant,
clippy::upper_case_acronyms
)]
#![feature(let_chains)] #![feature(let_chains)]
#![deny(clippy::unwrap_used, clippy::expect_used)] #![deny(clippy::unwrap_used, clippy::expect_used)]
mod app; mod app;
@ -6,8 +11,8 @@ mod routes;
use saleor_app_sdk::{ use saleor_app_sdk::{
config::Config, config::Config,
manifest::{cargo_info, AppManifest, AppPermission}, manifest::{cargo_info, AppManifestBuilder, AppPermission},
webhooks::{AsyncWebhookEventType, WebhookManifest}, webhooks::{AsyncWebhookEventType, WebhookManifestBuilder},
SaleorApp, SaleorApp,
}; };
use std::sync::Arc; use std::sync::Arc;
@ -23,7 +28,7 @@ use crate::{
#[tokio::main] #[tokio::main]
async fn main() -> anyhow::Result<()> { async fn main() -> anyhow::Result<()> {
let config = Config::load()?; let config = Config::load()?;
trace_to_std(&config); trace_to_std(&config)?;
let sitemap_config = SitemapConfig::load()?; let sitemap_config = SitemapConfig::load()?;
debug!("Creating configs..."); debug!("Creating configs...");
@ -31,13 +36,13 @@ async fn main() -> anyhow::Result<()> {
debug!("Creating saleor App..."); debug!("Creating saleor App...");
let app_manifest = AppManifest::new(&config, cargo_info!()) let app_manifest = AppManifestBuilder::new(&config, cargo_info!())
.add_permissions(vec![ .add_permissions(vec![
AppPermission::ManageProducts, AppPermission::ManageProducts,
AppPermission::ManagePages, AppPermission::ManagePages,
]) ])
.add_webhook( .add_webhook(
WebhookManifest::new(&config) WebhookManifestBuilder::new(&config)
.set_query(EVENTS_QUERY) .set_query(EVENTS_QUERY)
.add_async_events(vec![ .add_async_events(vec![
AsyncWebhookEventType::ProductCreated, AsyncWebhookEventType::ProductCreated,
@ -98,7 +103,7 @@ async fn main() -> anyhow::Result<()> {
.unwrap_or(&"3000"), .unwrap_or(&"3000"),
) )
.await?; .await?;
info!("listening on {}", listener.local_addr().unwrap()); info!("listening on {}", listener.local_addr()?);
match axum::serve(listener, app).await { match axum::serve(listener, app).await {
Ok(o) => Ok(o), Ok(o) => Ok(o),
Err(e) => anyhow::bail!(e), Err(e) => anyhow::bail!(e),

View file

@ -23,7 +23,7 @@ pub fn create_routes(state: AppState) -> Router {
} }
let service = handle_404.into_service(); let service = handle_404.into_service();
//TODO : Fix this relative path issue in workspaces //TODO : Fix this relative path issue in workspaces
let serve_dir = ServeDir::new("./sitemap-generator/public").not_found_service(service); let serve_dir = ServeDir::new("./public").not_found_service(service);
//TODO Query for everything using the app auth token //TODO Query for everything using the app auth token
//TODO "Failed fetching initial products: More than one channel exists, please spocify which //TODO "Failed fetching initial products: More than one channel exists, please spocify which

View file

@ -198,10 +198,7 @@ pub async fn regenerate(state: AppState, saleor_api_url: String) -> anyhow::Resu
category: match xml_data.iter().find(|all| { category: match xml_data.iter().find(|all| {
x.relations x.relations
.iter() .iter()
.find(|rel| { .any(|rel| all.id == *rel && all.data_type == XmlDataType::Category)
all.id == **rel && all.data_type == XmlDataType::Category
})
.is_some()
}) { }) {
Some(c) => Some(event_subjects_updated::Category { Some(c) => Some(event_subjects_updated::Category {
slug: c.slug.clone(), slug: c.slug.clone(),
@ -286,34 +283,27 @@ async fn get_all_pages(
); );
//Keep fetching next page //Keep fetching next page
let mut next_cursor = pages.page_info.end_cursor.clone(); let mut next_cursor = pages.page_info.end_cursor.clone();
loop { while let Some(cursor) = &mut next_cursor {
if let Some(cursor) = &mut next_cursor { let res = surf::post(saleor_api_url)
let res = surf::post(saleor_api_url) .header("authorization-bearer", token)
.header("authorization-bearer", token) .run_graphql(GetPagesNext::build(GetPagesNextVariables { after: cursor }))
.run_graphql(GetPagesNext::build(GetPagesNextVariables { after: cursor })) .await;
.await; if let Ok(query) = &res
if let Ok(query) = &res && let Some(data) = &query.data
&& let Some(data) = &query.data && let Some(pages) = &data.pages
&& let Some(pages) = &data.pages {
{ all_pages.append(
all_pages.append( &mut pages
&mut pages .edges
.edges .iter()
.iter() .map(|p| p.node.clone())
.map(|p| p.node.clone()) .collect::<Vec<_>>(),
.collect::<Vec<_>>(), );
); debug!("fetched next pages, eg.:{:?}", &pages.edges.first());
debug!("fetched next pages, eg.:{:?}", &pages.edges.first()); next_cursor.clone_from(&pages.page_info.end_cursor);
if !pages.page_info.has_next_page {
break;
}
next_cursor.clone_from(&pages.page_info.end_cursor);
} else {
error!("Failed fetching initial pages! {:?}", &res);
anyhow::bail!("Failed fetching initial pages! {:?}", res);
}
} else { } else {
break; error!("Failed fetching next pages! {:?}", &res);
anyhow::bail!("Failed fetching next pages! {:?}", res);
} }
} }
} else { } else {
@ -349,44 +339,37 @@ async fn get_all_categories(saleor_api_url: &str, token: &str) -> anyhow::Result
); );
//Keep fetching next page //Keep fetching next page
let mut next_cursor = categories.page_info.end_cursor.clone(); let mut next_cursor = categories.page_info.end_cursor.clone();
loop { while let Some(cursor) = &mut next_cursor {
if let Some(cursor) = &mut next_cursor { let res = surf::post(saleor_api_url)
let res = surf::post(saleor_api_url) .header("authorization-bearer", token)
.header("authorization-bearer", token) .run_graphql(GetCategoriesNext::build(GetCategoriesNextVariables {
.run_graphql(GetCategoriesNext::build(GetCategoriesNextVariables { after: Some(cursor),
after: Some(cursor), }))
})) .await;
.await; if let Ok(query) = &res
if let Ok(query) = &res && let Some(data) = &query.data
&& let Some(data) = &query.data && let Some(categories) = &data.categories
&& let Some(categories) = &data.categories {
{ all_categories.append(
all_categories.append( &mut categories
&mut categories .edges
.edges .iter()
.iter() .map(|p| p.node.clone())
.map(|p| p.node.clone()) .collect::<Vec<_>>(),
.collect::<Vec<_>>(), );
); debug!(
debug!( "fetched first categories, eg.:{:?}",
"fetched first categories, eg.:{:?}", &categories.edges.first()
&categories.edges.first() );
); next_cursor.clone_from(&categories.page_info.end_cursor);
if !categories.page_info.has_next_page {
break;
}
next_cursor.clone_from(&categories.page_info.end_cursor);
} else {
error!("Failed fetching initial pages! {:?}", &res);
anyhow::bail!("Failed fetching initial pages! {:?}", res);
}
} else { } else {
break; error!("Failed fetching next categories! {:?}", &res);
anyhow::bail!("Failed fetching next categories! {:?}", res);
} }
} }
} else { } else {
error!("Failed fetching initial pages! {:?}", &res); error!("Failed fetching initial Categories! {:?}", &res);
anyhow::bail!("Failed fetching initial pages! {:?}", res); anyhow::bail!("Failed fetching initial Categories! {:?}", res);
}; };
info!("All categories collected"); info!("All categories collected");
Ok(all_categories) Ok(all_categories)
@ -418,39 +401,32 @@ async fn get_all_collections(saleor_api_url: &str, token: &str) -> anyhow::Resul
//Keep fetching next page //Keep fetching next page
let mut next_cursor = collections.page_info.end_cursor.clone(); let mut next_cursor = collections.page_info.end_cursor.clone();
loop { while let Some(cursor) = &mut next_cursor {
if let Some(cursor) = &mut next_cursor { let res = surf::post(saleor_api_url)
let res = surf::post(saleor_api_url) .header("authorization-bearer", token)
.header("authorization-bearer", token) .run_graphql(GetCollectionsNext::build(GetCollectionsNextVariables {
.run_graphql(GetCollectionsNext::build(GetCollectionsNextVariables { after: Some(cursor),
after: Some(cursor), }))
})) .await;
.await; if let Ok(query) = &res
if let Ok(query) = &res && let Some(data) = &query.data
&& let Some(data) = &query.data && let Some(collections) = &data.collections
&& let Some(collections) = &data.collections {
{ all_collections.append(
all_collections.append( &mut collections
&mut collections .edges
.edges .iter()
.iter() .map(|p| p.node.clone())
.map(|p| p.node.clone()) .collect::<Vec<_>>(),
.collect::<Vec<_>>(), );
); debug!(
debug!( "fetched next collections, eg.:{:?}",
"fetched next collections, eg.:{:?}", &collections.edges.first()
&collections.edges.first() );
); next_cursor.clone_from(&collections.page_info.end_cursor);
if !collections.page_info.has_next_page {
break;
}
next_cursor.clone_from(&collections.page_info.end_cursor);
} else {
error!("Failed fetching initial collecnios! {:?}", &res);
anyhow::bail!("Failed fetching initial collections! {:?}", res);
}
} else { } else {
break; error!("Failed fetching next collecnios! {:?}", &res);
anyhow::bail!("Failed fetching next collections! {:?}", res);
} }
} }
} else { } else {
@ -498,7 +474,7 @@ async fn get_all_products(
debug!("fetched first products, eg: {:?}", products.edges.first()); debug!("fetched first products, eg: {:?}", products.edges.first());
let mut next_cursor = products.page_info.end_cursor.clone(); let mut next_cursor = products.page_info.end_cursor.clone();
loop { loop {
if let Some(cursor) = &mut next_cursor { while let Some(cursor) = &mut next_cursor {
let res = surf::post(saleor_api_url) let res = surf::post(saleor_api_url)
.header("authorization-bearer", token) .header("authorization-bearer", token)
.run_graphql(GetCategoryProductsNext::build( .run_graphql(GetCategoryProductsNext::build(
@ -526,22 +502,14 @@ async fn get_all_products(
.collect::<Vec<_>>(), .collect::<Vec<_>>(),
); );
debug!("fetched next products, eg: {:?}", products.edges.first()); debug!("fetched next products, eg: {:?}", products.edges.first());
if !products.page_info.has_next_page {
break;
}
next_cursor.clone_from(&products.page_info.end_cursor); next_cursor.clone_from(&products.page_info.end_cursor);
} else { } else {
error!("Failed fetching initial products! {:?}", &res); error!("Failed fetching initial products! {:?}", &res);
anyhow::bail!("Failed fetching initial products! {:?}", res); anyhow::bail!("Failed fetching initial products! {:?}", res);
} }
} else {
break;
} }
} }
} else { }
error!("Failed fetching initial products! {:?}", &res);
anyhow::bail!("Failed fetching initial products! {:?}", res);
};
info!("All products collected..."); info!("All products collected...");
Ok(all_categorised_products) Ok(all_categorised_products)
} }

View file

@ -13,12 +13,7 @@ use saleor_app_sdk::{
AsyncWebhookEventType, AsyncWebhookEventType,
}, },
}; };
use sitemap_rs::{ use sitemap_rs::{sitemap::Sitemap, sitemap_index::SitemapIndex, url::Url, url_set::UrlSet};
sitemap::Sitemap,
sitemap_index::SitemapIndex,
url::{Url},
url_set::UrlSet,
};
use tinytemplate::TinyTemplate; use tinytemplate::TinyTemplate;
use tokio::spawn; use tokio::spawn;
use tracing::{debug, error, info}; use tracing::{debug, error, info};
@ -26,8 +21,8 @@ use tracing::{debug, error, info};
use crate::{ use crate::{
app::{AppError, AppState, XmlData, XmlDataType}, app::{AppError, AppState, XmlData, XmlDataType},
queries::event_subjects_updated::{ queries::event_subjects_updated::{
Category, Category2, CategoryUpdated, Collection, CollectionUpdated, Page, Category, Category2, CategoryUpdated, Collection, CollectionUpdated, Page, PageUpdated,
PageUpdated, Product, ProductUpdated, Product, ProductUpdated,
}, },
}; };
@ -46,8 +41,8 @@ pub async fn webhooks(
.to_str()? .to_str()?
.to_owned(); .to_owned();
let event_type = get_webhook_event_type(&headers)?; let event_type = get_webhook_event_type(&headers)?;
match event_type { if let EitherWebhookType::Async(a) = event_type {
EitherWebhookType::Async(a) => match a { match a {
AsyncWebhookEventType::ProductUpdated AsyncWebhookEventType::ProductUpdated
| AsyncWebhookEventType::ProductCreated | AsyncWebhookEventType::ProductCreated
| AsyncWebhookEventType::ProductDeleted => { | AsyncWebhookEventType::ProductDeleted => {
@ -90,8 +85,7 @@ pub async fn webhooks(
} }
_ => (), _ => (),
}, }
_ => (),
} }
info!("webhook proccessed"); info!("webhook proccessed");
@ -185,10 +179,7 @@ async fn update_sitemap_product(
xml_cat.last_modified = chrono::offset::Utc::now().fixed_offset(); xml_cat.last_modified = chrono::offset::Utc::now().fixed_offset();
// If the category exists but product isn't in relation to it yet, // If the category exists but product isn't in relation to it yet,
// add it // add it
if !xml_cat if !xml_cat.relations.iter().any(|c| *c == product.id) {
.relations
.iter().any(|c| *c == product.id)
{
xml_cat.relations.push(product.id.clone()); xml_cat.relations.push(product.id.clone());
} }
//if cat isn't in xml data, add it //if cat isn't in xml data, add it
@ -216,10 +207,7 @@ async fn update_sitemap_product(
category: match xml_data.iter().find(|all| { category: match xml_data.iter().find(|all| {
x.relations x.relations
.iter() .iter()
.find(|rel| { .any(|rel| all.id == *rel && all.data_type == XmlDataType::Category)
all.id == **rel && all.data_type == XmlDataType::Category
})
.is_some()
}) { }) {
Some(c) => Some(Category { Some(c) => Some(Category {
slug: c.slug.clone(), slug: c.slug.clone(),
@ -322,49 +310,38 @@ async fn update_sitemap_category(
let mut tt = TinyTemplate::new(); let mut tt = TinyTemplate::new();
if is_category_in_product_url && x.data_type == XmlDataType::Product { if is_category_in_product_url && x.data_type == XmlDataType::Product {
tt.add_template("product_url", &state.sitemap_config.product_template)?; tt.add_template("product_url", &state.sitemap_config.product_template)?;
let context;
//If current xml products category is this changed category, just use that instead //If current xml products category is this changed category, just use that instead
//of searching for it again //of searching for it again
match x.relations.iter().find(|c| *c == &category.id) { let context = ProductUpdated {
Some(_) => { product: match x.relations.iter().find(|c| *c == &category.id) {
context = ProductUpdated { Some(_) => Some(Product {
product: Some(Product { id: x.id.clone(),
id: x.id.clone(), slug: x.slug.clone(),
slug: x.slug.clone(), category: Some(Category {
category: Some(Category { slug: category.slug.clone(),
slug: category.slug.clone(), id: category.id.clone(),
id: category.id.clone(), }),
}),
None => Some(Product {
id: x.id.clone(),
slug: x.slug.clone(),
category: match xml_data.iter().find(|all| {
x.relations.iter().any(|rel| {
all.id == *rel && all.data_type == XmlDataType::Category
})
}) {
Some(c) => Some(Category {
slug: c.slug.clone(),
id: c.id.clone(),
}), }),
}), None => Some(Category {
}; slug: "unknown".to_owned(),
} id: cynic::Id::new("unknown".to_owned()),
None => { }),
context = ProductUpdated { },
product: Some(Product { }),
id: x.id.clone(), },
slug: x.slug.clone(), };
category: match xml_data.iter().find(|all| {
x.relations
.iter()
.find(|rel| {
all.id == **rel
&& all.data_type == XmlDataType::Category
})
.is_some()
}) {
Some(c) => Some(Category {
slug: c.slug.clone(),
id: c.id.clone(),
}),
None => Some(Category {
slug: "unknown".to_owned(),
id: cynic::Id::new("unknown".to_owned()),
}),
},
}),
};
}
}
product_urls.push( product_urls.push(
Url::builder(tt.render("product_url", &context)?) Url::builder(tt.render("product_url", &context)?)
.last_modified(x.last_modified) .last_modified(x.last_modified)
@ -564,8 +541,7 @@ pub async fn write_xml(
let mut sitemaps: Vec<UrlSet> = vec![]; let mut sitemaps: Vec<UrlSet> = vec![];
for urls in sliced_urls { for urls in sliced_urls {
for url in urls.iter().cloned() { for url in urls.iter().cloned() {
let mut sitemap_urls: Vec<Url> = vec![]; let sitemap_urls = vec![url];
sitemap_urls.push(url);
sitemaps.push(UrlSet::new(sitemap_urls)?); sitemaps.push(UrlSet::new(sitemap_urls)?);
} }
} }
@ -616,22 +592,25 @@ async fn update_sitemap_index(state: &AppState) -> anyhow::Result<()> {
let sitemaps: Vec<Sitemap> = paths let sitemaps: Vec<Sitemap> = paths
.into_iter() .into_iter()
.map(|p| { .filter_map(|p| {
Sitemap::new( if let Some(file_name) = p.file_name() {
format!( Some(Sitemap::new(
"{}/{}", format!(
state.sitemap_config.index_hostname, "{}/{}",
p.file_name() state.sitemap_config.index_hostname,
.expect("file dissapeared or broke during sitemap-index construction") file_name.to_string_lossy()
.to_string_lossy() ),
), p.metadata().map_or(None, |meta| {
p.metadata().map_or(None, |meta| { meta.modified().map_or(None, |modified| {
meta.modified().map_or(None, |modified| { let dt_utc: DateTime<Utc> = modified.into();
let dt_utc: DateTime<Utc> = modified.into(); Some(dt_utc.fixed_offset())
Some(dt_utc.fixed_offset()) })
}) }),
}), ))
) } else {
error!("file dissapeared or broke during sitemap-index construction");
None
}
}) })
.collect::<Vec<_>>(); .collect::<Vec<_>>();
let sitemap_index = SitemapIndex::new(sitemaps)?; let sitemap_index = SitemapIndex::new(sitemaps)?;
@ -646,7 +625,7 @@ async fn update_sitemap_index(state: &AppState) -> anyhow::Result<()> {
let mut buf = Vec::<u8>::new(); let mut buf = Vec::<u8>::new();
sitemap_index.write(&mut buf)?; sitemap_index.write(&mut buf)?;
file.write_all(&mut buf).await?; file.write_all(&buf).await?;
Ok(()) Ok(())
} }