This commit is contained in:
Matthieu Bessat 2024-10-15 12:10:46 +02:00
parent 66b3a99814
commit 98be8dd574
13 changed files with 329 additions and 46 deletions

View file

@ -1,10 +1,12 @@
use argh::FromArgs;
use anyhow::{anyhow, Context, Result};
use log::info;
use crate::{get_app_context, server::{start_http_server, ServerConfig}};
#[derive(Debug, FromArgs)]
/// Autotasker daemon
struct CliFlags {
struct ServerCliFlags {
/// path to YAML config file to use to configure autotasker
#[argh(option)]
config: Option<String>,
@ -25,4 +27,22 @@ struct CliFlags {
listen_port: u32
}
const DEFAULT_ASSETS_PATH: &'static str = &"/usr/local/lib/autotasker/assets";
pub async fn start_server_cli() -> Result<()> {
info!("Starting minauth");
let flags: ServerCliFlags = argh::from_env();
let (config, db_pool) = get_app_context(crate::StartAppConfig {
config_path: flags.config,
database_path: flags.database
}).await.context("Getting app context")?;
start_http_server(
ServerConfig {
assets_path: flags.static_assets.unwrap_or(DEFAULT_ASSETS_PATH.to_string()),
listen_host: flags.listen_host,
listen_port: flags.listen_port
},
config,
db_pool
).await
}

View file

@ -0,0 +1,17 @@
use axum::{extract::State, response::{Html, IntoResponse}};
use axum_macros::debug_handler;
use minijinja::context;
use crate::server::AppState;
#[debug_handler]
pub async fn home(
State(app_state): State<AppState>
) -> impl IntoResponse {
Html(
app_state.templating_env.get_template("pages/home.html").unwrap()
.render(context!())
.unwrap()
)
}

View file

@ -0,0 +1,26 @@
use axum::{extract::State, response::{Html, IntoResponse}};
use minijinja::context;
use crate::server::AppState;
pub async fn login_form(
State(app_state): State<AppState>
) -> impl IntoResponse {
Html(
app_state.templating_env.get_template("pages/home.html").unwrap()
.render(context!())
.unwrap()
)
}
pub async fn perform_login(
State(app_state): State<AppState>
) -> impl IntoResponse {
Html(
app_state.templating_env.get_template("pages/home.html").unwrap()
.render(context!())
.unwrap()
)
}

View file

@ -1,3 +1,4 @@
pub mod home;
pub mod authorize;
pub mod login;
pub mod register;

19
src/database.rs Normal file
View file

@ -0,0 +1,19 @@
use anyhow::{anyhow, Context, Result};
use sqlx::{sqlite::{SqliteConnectOptions, SqlitePoolOptions}, Pool, Sqlite, ConnectOptions};
use std::str::FromStr;
pub async fn prepare_database(sqlite_db_path: &str) -> Result<Pool<Sqlite>> {
let conn_str = format!("sqlite:{}", sqlite_db_path);
let pool = SqlitePoolOptions::new()
.max_connections(50)
.connect_with(
SqliteConnectOptions::from_str(&conn_str)?
.log_statements(log::LevelFilter::Trace)
)
.await
.context("could not connect to database_url")?;
Ok(pool)
}

View file

@ -1,20 +1,51 @@
pub mod models;
pub mod controllers;
pub mod router;
pub mod server;
pub mod database;
pub mod cli;
use std::fs;
use anyhow::{Result, Context, anyhow};
use database::prepare_database;
use log::info;
use sqlx::sqlite::{SqliteConnectOptions, SqlitePoolOptions};
use sqlx::{ConnectOptions, Pool, Sqlite};
use models::config::Config;
use minijinja::{context, Environment};
const DEFAULT_DB_PATH: &'static str = &"/var/lib/autotasker/autotasker.db";
const DEFAULT_ASSETS_PATH: &'static str = &"/usr/local/lib/autotasker/assets";
const DEFAULT_CONFIG_PATH: &'static str = &"/etc/autotasker/config.yaml";
#[derive(Debug, Clone)]
pub struct AppState {
config: Config,
db: Pool<Sqlite>,
templating_env: Environment<'static>
fn get_config(path: String) -> Result<Config> {
let inp_def_yaml = fs::read_to_string(path)
.expect("Should have been able to read the the config file");
toml::from_str(&inp_def_yaml)
.map_err(|e| anyhow!("Failed to parse config, {:?}", e))
}
fn main() {
println!("Hello, world!");
struct StartAppConfig {
config_path: Option<String>,
database_path: Option<String>,
}
#[tokio::main]
async fn main() -> Result<()> {
cli::start_server_cli().await
}
async fn get_app_context(start_app_config: StartAppConfig) -> Result<(Config, Pool<Sqlite>)> {
env_logger::init();
let pool = prepare_database(
&start_app_config.database_path.unwrap_or(DEFAULT_DB_PATH.to_string())
).await.context("Could not prepare db")?;
let config_path = start_app_config.config_path.unwrap_or(DEFAULT_CONFIG_PATH.to_string());
info!("Using config file at {}", &config_path);
let config: Config = get_config(config_path).expect("Cannot get config");
Ok((config, pool))
}

10
src/router.rs Normal file
View file

@ -0,0 +1,10 @@
use axum::{routing::{get, post}, Router};
use crate::{controllers::ui, server::AppState};
pub fn build_router() -> Router<AppState> {
Router::new()
.route("/", get(ui::home::home))
.route("/login", get(ui::login::login_form))
.route("/login", post(ui::login::perform_login))
}

View file

@ -1,27 +1,18 @@
use fully_pub::fully_pub;
use tower_http::services::ServeDir;
use anyhow::{Result, Context, anyhow};
use log::info;
use minijinja::{context, Environment};
use sqlx::{Pool, Sqlite};
use crate::{models::config::Config, router::build_router};
fn get_config(path: String) -> Result<Config> {
let inp_def_yaml = fs::read_to_string(path)
.expect("Should have been able to read the the config file");
serde_yaml::from_str(&inp_def_yaml)
.map_err(|e| anyhow!("Failed to parse config, {:?}", e))
}
fn build_templating_env() -> Environment<'static> {
fn build_templating_env(config: &Config) -> Environment<'static> {
let mut templating_env = Environment::new();
let _ = templating_env
.add_template("layouts/base.html", include_str!("./templates/layouts/base.html"));
let _ = templating_env
.add_template("pages/home.html", include_str!("./templates/pages/home.html"));
let _ = templating_env
.add_template("pages/list_tasks.html", include_str!("./templates/pages/list_tasks.html"));
let _ = templating_env
.add_template("pages/list_task_runs.html", include_str!("./templates/pages/list_task_runs.html"));
let _ = templating_env
.add_template("pages/task_run_details.html", include_str!("./templates/pages/task_run_details.html"));
let _ = templating_env
.add_template("pages/run_task.html", include_str!("./templates/pages/run_task.html"));
// TODO: better loading with embed https://docs.rs/minijinja-embed/latest/minijinja_embed/
templating_env.add_global("gl", context! {
@ -32,3 +23,44 @@ fn build_templating_env() -> Environment<'static> {
templating_env
}
#[derive(Debug)]
#[fully_pub]
/// HTTP server arguments
pub struct ServerConfig {
listen_host: String,
listen_port: u32,
assets_path: String
}
#[derive(Debug, Clone)]
#[fully_pub]
pub struct AppState {
config: Config,
db: Pool<Sqlite>,
templating_env: Environment<'static>
}
pub async fn start_http_server(server_config: ServerConfig, config: Config, db_pool: Pool<Sqlite>) -> Result<()> {
// build state
let state = AppState {
templating_env: build_templating_env(&config),
config,
db: db_pool
};
// build routes
let services = build_router()
.nest_service(
"/assets",
ServeDir::new(server_config.assets_path)
)
.with_state(state);
let listen_addr = format!("{}:{}", server_config.listen_host, server_config.listen_port);
info!("Starting web server on http://{}", &listen_addr);
let listener = tokio::net::TcpListener::bind(listen_addr).await.unwrap();
axum::serve(listener, services).await.context("Axum serve")?;
Ok(())
}

View file

@ -0,0 +1,22 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Minauth</title>
<link href="/assets/styles/simple.css" rel="stylesheet">
<link href="/assets/styles/app.css" rel="stylesheet">
</head>
<body>
<header>
Minauth
</header>
<main class="container">
{% block body %}{% endblock %}
</main>
<footer>
Minauth {{ gl.instance.version }}
</footer>
</body>
</html>

View file

@ -0,0 +1,11 @@
{% extends "layouts/base.html" %}
{% block body %}
<h1>Bienvenue sur Minauth</h1>
<p>
Minauth is free software under <a href="https://www.gnu.org/licenses/gpl-3.0.txt">GPLv3</a> licence.
You can find source code on a <a href="https://forge.lefuturiste.fr/mbess/minauth">self-hosted forge repository</a>.
</p>
{% endblock %}

View file