codoseo-web 0.1.0-rc.1

The CodoSEO web app: axum routes, askama templates and htmx partials.
Documentation
//! `/account`: who is signed in, their plan, sign out, and (self-hosted owner) whether new
//! people may sign up.

use askama::Template;
use axum::extract::State;
use axum::response::{IntoResponse, Redirect, Response};
use axum::routing::{get, post};
use axum::{Form, Router};
use serde::Deserialize;

use crate::auth::CurrentUser;
use crate::config::Mode;
use crate::error::AppError;
use crate::layout::{Screen, Shell};
use crate::render::html;
use crate::state::AppState;

pub fn routes() -> Router<AppState> {
    Router::new()
        .route("/account", get(page))
        .route("/account/signups", post(set_signups))
}

#[derive(Template)]
#[template(path = "account/index.html")]
pub struct AccountPage {
    pub shell: Shell,
    pub email: String,
    pub plan: String,
    pub self_hosted: bool,
    pub billing: bool,
    pub is_owner: bool,
    pub signups_open: bool,
    pub is_admin: bool,
}

async fn page(State(state): State<AppState>, user: CurrentUser) -> Result<Response, AppError> {
    let shell = Shell::load(&state, &user, None, Screen::Account).await?;
    let signups_open = codoseo_store::accounts::signups_open(&state.pool).await?;
    Ok(html(&AccountPage {
        plan: shell.user.plan_label.clone(),
        shell,
        email: user.account.email.clone(),
        self_hosted: state.config.mode == Mode::SelfHost,
        billing: state.config.mode == Mode::Cloud,
        is_owner: user.account.is_owner,
        signups_open,
        is_admin: super::admin::is_admin(&state, &user.account),
    })?
    .into_response())
}

#[derive(Deserialize)]
struct SignupsForm {
    open: String,
}

async fn set_signups(
    State(state): State<AppState>,
    user: CurrentUser,
    Form(form): Form<SignupsForm>,
) -> Result<Response, AppError> {
    if state.config.mode != Mode::SelfHost || !user.account.is_owner {
        return Err(AppError::Forbidden(
            "Only the instance owner can change signups.".to_owned(),
        ));
    }
    codoseo_store::accounts::set_signups_open(&state.pool, form.open == "true").await?;
    Ok(Redirect::to("/account").into_response())
}