zero4rs 2.0.0

zero4rs is a powerful, pragmatic, and extremely fast web framework for Rust
Documentation
use crate::prelude2::*;

use crate::commons::validatorr::validation_flatten;
use crate::core::auth0::UserId;
use crate::idempotency::{IdempotencyKey, NextAction};
use crate::services::idempotency_service::save_response;
use crate::services::idempotency_service::try_processing;
use crate::services::newsletter_service::enqueue_delivery_tasks;
use crate::services::newsletter_service::get_confirmed_subscribers;
use crate::services::newsletter_service::insert_newsletter_issue;
use crate::services::newsletter_service::NewsletterFormData;
use actix_web_flash_messages::IncomingFlashMessages;

fn success_message() {
    crate::core::flash_messages::send_flash_message("The newsletter issue has been published!");
}

fn success_message_v2() {
    crate::core::flash_messages::send_flash_message(
        "The newsletter issue has been accepted - \
        emails will go out shortly.",
    );
}

pub async fn publish_newsletter_form(
    _app_state: web::Data<AppContext>,
    _user_id: web::ReqData<UserId>,
    flash_messages: IncomingFlashMessages,
    request: HttpRequest,
) -> impl Responder {
    let idempotency_key = uuid::Uuid::now_v7();
    let _flash_html = crate::core::flash_messages::get_flash_message_html(&flash_messages);

    let mut ctx = tera::Context::new();
    ctx.insert("__flash_message", &_flash_html);
    ctx.insert("idempotency_key", &idempotency_key);

    request.render(200, "subscription/newsletter.html", ctx)
}

pub async fn publish_newsletter(
    form: web::Form<NewsletterFormData>,
    app_state: web::Data<AppContext>,
    user_id: web::ReqData<UserId>,
    request: HttpRequest,
) -> Result<HttpResponse> {
    if let Some(err) = validation_flatten(&form.0) {
        return request.json(200, R::invalid(err));
    }

    let NewsletterFormData {
        title,
        html_content,
        idempotency_key,
    } = form.0;

    let _idempotency_key: IdempotencyKey =
        idempotency_key.try_into().map_err(Error::UnexpectedError)?;

    let user_id = user_id.into_inner();

    let ll = try_processing(app_state.mysql(), &_idempotency_key, user_id.to_string()).await?;

    match ll {
        NextAction::StartProcessing(_) => {}
        NextAction::ReturnSavedResponse(saved_response) => {
            success_message();
            return Ok(saved_response);
        }
    }

    let _subscribers = get_confirmed_subscribers(app_state.mysql())
        .await
        .map_err(Error::run_time)?;

    for subscriber in _subscribers {
        match subscriber {
            Ok(subscriber) => {
                let _receive_email = subscriber.email;

                match app_state
                    .email_client
                    .send_email(&_receive_email, &title, &html_content)
                    .await
                {
                    Ok(_) => (),
                    Err(e) => {
                        log::error!(
                            "Send email failed: receiver={}, title={}, error_message={:?}",
                            &_receive_email,
                            &title,
                            e
                        );
                    }
                }
            }
            Err(e) => {
                log::error!(
                    "Skipping a confirmed subscriber. \
                    Their stored contact emails are invalid, error={}",
                    e
                );
            }
        }
    }

    // send flash message
    success_message();

    let response = crate::utils::see_other("/newsletters");

    let response = save_response(
        app_state.mysql(),
        &_idempotency_key,
        user_id.to_string(),
        response,
    )
    .await?;

    Ok(response)
}

pub async fn publish_newsletter_v2(
    form: web::Form<NewsletterFormData>,
    app_state: web::Data<AppContext>,
    user_id: web::ReqData<UserId>,
    request: HttpRequest,
) -> Result<HttpResponse> {
    if let Some(err) = validation_flatten(&form.0) {
        return request.json(200, R::invalid(err));
    }

    let NewsletterFormData {
        title,
        html_content,
        idempotency_key,
    } = form.0;

    let _idempotency_key: IdempotencyKey =
        idempotency_key.try_into().map_err(Error::UnexpectedError)?;

    let user_id = user_id.into_inner();

    let ll = try_processing(app_state.mysql(), &_idempotency_key, user_id.to_string()).await?;

    let mut transaction = match ll {
        NextAction::StartProcessing(t) => t,
        NextAction::ReturnSavedResponse(saved_response) => {
            success_message_v2();
            return Ok(saved_response);
        }
    };

    let _issue_id = insert_newsletter_issue(&mut transaction, &title, &html_content)
        .await
        .context("Failed to store newsletter issue details")
        .map_err(Error::UnexpectedError)?;

    enqueue_delivery_tasks(&mut transaction, _issue_id)
        .await
        .context("Failed to enqueue delivery tasks")
        .map_err(Error::UnexpectedError)?;

    let response = crate::utils::see_other("/newsletters");

    let response = save_response(
        app_state.mysql(),
        &_idempotency_key,
        user_id.to_string(),
        response,
    )
    .await?;

    // if commit or rollback have not been called before the Transaction object goes out of scope (i.e. Drop is invoked),
    // a rollback command is queued to be executed as soon as an opportunity arises.
    transaction
        .commit()
        .await
        .context("Failed to commit SQL transaction for publish_newsletter_v2.")
        .map_err(Error::UnexpectedError)?;

    // send flash message
    success_message_v2();

    Ok(response)
}