More modularization
This commit is contained in:
parent
2052092796
commit
96575cf40b
14 changed files with 267 additions and 133 deletions
101
Cargo.lock
generated
101
Cargo.lock
generated
|
@ -26,6 +26,55 @@ dependencies = [
|
|||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstream"
|
||||
version = "0.6.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "418c75fa768af9c03be99d17643f93f79bbba589895012a80e3452a19ddda15b"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"anstyle-parse",
|
||||
"anstyle-query",
|
||||
"anstyle-wincon",
|
||||
"colorchoice",
|
||||
"is_terminal_polyfill",
|
||||
"utf8parse",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstyle"
|
||||
version = "1.0.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "038dfcf04a5feb68e9c60b21c9625a54c2c0616e79b72b0fd87075a056ae1d1b"
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-parse"
|
||||
version = "0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c03a11a9034d92058ceb6ee011ce58af4a9bf61491aa7e1e59ecd24bd40d22d4"
|
||||
dependencies = [
|
||||
"utf8parse",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-query"
|
||||
version = "1.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a64c907d4e79225ac72e2a354c9ce84d50ebb4586dee56c82b3ee73004f537f5"
|
||||
dependencies = [
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-wincon"
|
||||
version = "3.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "61a38449feb7068f52bb06c12759005cf459ee52bb4adc1d5a7c4322d716fb19"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "async-trait"
|
||||
version = "0.1.80"
|
||||
|
@ -180,6 +229,33 @@ version = "1.0.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.5.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "90bc066a67923782aa8515dbaea16946c5bcc5addbd668bb80af688e53e548a0"
|
||||
dependencies = [
|
||||
"clap_builder",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_builder"
|
||||
version = "4.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ae129e2e766ae0ec03484e609954119f123cc1fe650337e155d03b022f24f7b4"
|
||||
dependencies = [
|
||||
"anstream",
|
||||
"anstyle",
|
||||
"clap_lex",
|
||||
"strsim",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_lex"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce"
|
||||
|
||||
[[package]]
|
||||
name = "color-eyre"
|
||||
version = "0.6.3"
|
||||
|
@ -207,6 +283,12 @@ dependencies = [
|
|||
"tracing-error",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "colorchoice"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b6a852b24ab71dffc585bcb46eaf7959d175cb865a7152e35b348d1b2960422"
|
||||
|
||||
[[package]]
|
||||
name = "config"
|
||||
version = "0.14.0"
|
||||
|
@ -676,6 +758,12 @@ dependencies = [
|
|||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "is_terminal_polyfill"
|
||||
version = "1.70.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f8478577c03552c21db0e2724ffb8986a5ce7af88107e6be5d2ee6e158c12800"
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "1.0.11"
|
||||
|
@ -752,6 +840,7 @@ name = "lyrs"
|
|||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"axum",
|
||||
"clap",
|
||||
"color-eyre",
|
||||
"config",
|
||||
"futures",
|
||||
|
@ -1365,6 +1454,12 @@ dependencies = [
|
|||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "strsim"
|
||||
version = "0.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.60"
|
||||
|
@ -1676,6 +1771,12 @@ version = "1.11.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202"
|
||||
|
||||
[[package]]
|
||||
name = "utf8parse"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a"
|
||||
|
||||
[[package]]
|
||||
name = "valuable"
|
||||
version = "0.1.0"
|
||||
|
|
|
@ -7,6 +7,7 @@ edition = "2021"
|
|||
|
||||
[dependencies]
|
||||
axum = { version = "0.7.5", features = ["macros", "tokio"] }
|
||||
clap = "4.5.4"
|
||||
color-eyre = "0.6.3"
|
||||
config = "0.14.0"
|
||||
futures = "0.3.30"
|
||||
|
|
1
src/cli.rs
Normal file
1
src/cli.rs
Normal file
|
@ -0,0 +1 @@
|
|||
|
28
src/error.rs
Normal file
28
src/error.rs
Normal file
|
@ -0,0 +1,28 @@
|
|||
use crate::prelude::*;
|
||||
use axum::{http::StatusCode, response::IntoResponse};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Error(pub Box<dyn std::error::Error>);
|
||||
|
||||
impl IntoResponse for Error {
|
||||
fn into_response(self) -> axum::http::Response<axum::body::Body> {
|
||||
error!("webserver error: {:?}", self.0);
|
||||
(
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
format!("internal server error: {}", self.0),
|
||||
)
|
||||
.into_response()
|
||||
}
|
||||
}
|
||||
|
||||
// This enables using `?` on functions that return `Result<_, anyhow::Error>`
|
||||
// to turn them into `Result<_, AppError>`. That way you don't need to do that
|
||||
// manually.
|
||||
impl<E> From<E> for Error
|
||||
where
|
||||
E: Into<Box<dyn std::error::Error>>,
|
||||
{
|
||||
fn from(err: E) -> Self {
|
||||
Self(err.into())
|
||||
}
|
||||
}
|
|
@ -1,22 +1,24 @@
|
|||
use std::path::Path;
|
||||
|
||||
use crate::prelude::*;
|
||||
use notify::{Config, Event, RecommendedWatcher, RecursiveMode, Watcher};
|
||||
use tokio::sync::mpsc::channel;
|
||||
use tracing::{error, info, instrument};
|
||||
use std::path::Path;
|
||||
use tokio::{sync::mpsc::channel, task::JoinHandle};
|
||||
use tracing::{error, info};
|
||||
|
||||
use crate::Besult;
|
||||
|
||||
pub type FileWatcher = RecommendedWatcher;
|
||||
pub type WatcherType = RecommendedWatcher;
|
||||
pub type FileWatcher = (WatcherType, JoinHandle<()>);
|
||||
|
||||
pub mod prelude {
|
||||
pub use super::{file_watcher, FileWatcher};
|
||||
#![allow(unused_imports)]
|
||||
pub use super::{file_monitor, file_watcher, FileWatcher};
|
||||
}
|
||||
|
||||
pub fn file_watcher<P, F>(dir: P, cb: F) -> Besult<FileWatcher>
|
||||
/// Notifies your callback for each individual event
|
||||
pub fn file_watcher<P, F>(dir: P, cb: F) -> Result<FileWatcher>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
F: Fn(Event) -> () + std::marker::Send + 'static,
|
||||
{
|
||||
// TODO: debounce?
|
||||
let (tx, mut rx) = channel(1);
|
||||
let mut watcher = RecommendedWatcher::new(
|
||||
move |res| match res {
|
||||
|
@ -30,7 +32,7 @@ where
|
|||
|
||||
info!("Watching directory '{}'", dir.as_ref().display());
|
||||
|
||||
tokio::spawn(async move {
|
||||
let handle = tokio::spawn(async move {
|
||||
while let Some(ev) = rx.recv().await {
|
||||
cb(ev)
|
||||
}
|
||||
|
@ -38,5 +40,37 @@ where
|
|||
|
||||
watcher.watch(dir.as_ref(), RecursiveMode::Recursive)?;
|
||||
|
||||
Ok(watcher)
|
||||
Ok((watcher, handle))
|
||||
}
|
||||
|
||||
/// Only know when something changes
|
||||
pub fn file_monitor<P, F>(dir: P, cb: F) -> Result<FileWatcher>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
F: Fn() -> () + std::marker::Send + 'static,
|
||||
{
|
||||
let (tx, mut rx) = tokio::sync::watch::channel(());
|
||||
let mut watcher = RecommendedWatcher::new(
|
||||
move |res| match res {
|
||||
Ok(_e) => futures::executor::block_on(async {
|
||||
tx.send_replace(());
|
||||
}),
|
||||
Err(e) => error!("Error from file_watcher: {e}"),
|
||||
},
|
||||
Config::default(),
|
||||
)?;
|
||||
|
||||
info!("Watching directory '{}'", dir.as_ref().display());
|
||||
|
||||
let handle = tokio::spawn(async move {
|
||||
while let Ok(_) = rx.changed().await {
|
||||
cb();
|
||||
// "good enough" debouncing
|
||||
rx.mark_unchanged();
|
||||
}
|
||||
});
|
||||
|
||||
watcher.watch(dir.as_ref(), RecursiveMode::Recursive)?;
|
||||
|
||||
Ok((watcher, handle))
|
||||
}
|
||||
|
|
|
@ -1,5 +0,0 @@
|
|||
use tower_livereload::LiveReloadLayer;
|
||||
|
||||
pub fn live_reload_layer() -> LiveReloadLayer {
|
||||
LiveReloadLayer::new()
|
||||
}
|
82
src/main.rs
82
src/main.rs
|
@ -1,85 +1,21 @@
|
|||
use axum::extract::State;
|
||||
use axum::response::IntoResponse;
|
||||
use axum::routing::get;
|
||||
use axum::{http::StatusCode, response::Html, serve, Router};
|
||||
use minijinja::context;
|
||||
use state::State as AppState;
|
||||
pub use tracing::{debug, error, event, info, span, warn, Level};
|
||||
|
||||
use crate::live_reload::live_reload_layer;
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Berr(Box<dyn std::error::Error>);
|
||||
type Besult<T> = std::result::Result<T, Berr>;
|
||||
use crate::prelude::*;
|
||||
|
||||
mod cli;
|
||||
mod error;
|
||||
mod file_watcher;
|
||||
mod live_reload;
|
||||
mod observe;
|
||||
mod prelude;
|
||||
mod result;
|
||||
mod router;
|
||||
mod state;
|
||||
mod static_files;
|
||||
mod tailwind;
|
||||
mod templates;
|
||||
mod webserver;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Besult<()> {
|
||||
async fn main() -> Result<()> {
|
||||
// load configuration?
|
||||
let _setup_logging = observe::setup_logging();
|
||||
|
||||
let state = AppState::try_new().await?;
|
||||
|
||||
// TODO: only start tailwind if in dev mode?
|
||||
tokio::spawn(async move { tailwind::start_watcher() });
|
||||
|
||||
let lr = live_reload_layer();
|
||||
|
||||
{
|
||||
// TODO: only start watcher for dev mode?
|
||||
let state = state.clone();
|
||||
let lr = lr.reloader();
|
||||
tokio::spawn(async move { state.templates.start_watcher(Some(lr)) });
|
||||
}
|
||||
|
||||
let (static_file_service, _static_file_watcher) = static_files::router(Some(lr.reloader()))?;
|
||||
|
||||
let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
|
||||
|
||||
info!("Listening on {listener:?}");
|
||||
let router = Router::new()
|
||||
.route("/", get(index))
|
||||
.nest_service("/static", static_file_service)
|
||||
.with_state(state);
|
||||
|
||||
Ok(serve(listener, router).await?)
|
||||
}
|
||||
|
||||
async fn index(State(state): State<AppState>) -> Besult<Html<String>> {
|
||||
Ok(Html(
|
||||
state
|
||||
.templates
|
||||
.render("pages/index.html.jinja", context!())
|
||||
.await?,
|
||||
))
|
||||
}
|
||||
|
||||
impl IntoResponse for Berr {
|
||||
fn into_response(self) -> axum::http::Response<axum::body::Body> {
|
||||
error!("webserver error: {:?}", self.0);
|
||||
(
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
format!("internal server error: {}", self.0),
|
||||
)
|
||||
.into_response()
|
||||
}
|
||||
}
|
||||
|
||||
// This enables using `?` on functions that return `Result<_, anyhow::Error>`
|
||||
// to turn them into `Result<_, AppError>`. That way you don't need to do that
|
||||
// manually.
|
||||
impl<E> From<E> for Berr
|
||||
where
|
||||
E: Into<Box<dyn std::error::Error>>,
|
||||
{
|
||||
fn from(err: E) -> Self {
|
||||
Self(err.into())
|
||||
}
|
||||
webserver::webserver(router::router().await?).await
|
||||
}
|
||||
|
|
5
src/prelude.rs
Normal file
5
src/prelude.rs
Normal file
|
@ -0,0 +1,5 @@
|
|||
#![allow(unused_imports)]
|
||||
|
||||
pub use crate::error::Error;
|
||||
pub use crate::result::Result;
|
||||
pub use tracing::{debug, error, event, info, span, warn, Level};
|
3
src/result.rs
Normal file
3
src/result.rs
Normal file
|
@ -0,0 +1,3 @@
|
|||
use crate::error::Error;
|
||||
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
31
src/router.rs
Normal file
31
src/router.rs
Normal file
|
@ -0,0 +1,31 @@
|
|||
use crate::{prelude::*, state::State as AppState, static_files};
|
||||
use axum::{extract::State, response::Html, routing::get, Router};
|
||||
use minijinja::context;
|
||||
use tower_livereload::LiveReloadLayer;
|
||||
|
||||
pub async fn router() -> Result<Router> {
|
||||
let state = AppState::try_new().await?;
|
||||
|
||||
let lr = LiveReloadLayer::new();
|
||||
let _template_file_watcher = state
|
||||
.clone()
|
||||
.templates
|
||||
.start_watcher(Some(lr.reloader()))
|
||||
.await?;
|
||||
let (static_file_service, _static_file_watcher) = static_files::router(Some(lr.reloader()))?;
|
||||
|
||||
Ok(Router::new()
|
||||
.route("/", get(index))
|
||||
.nest_service("/static", static_file_service)
|
||||
.layer(lr)
|
||||
.with_state(state))
|
||||
}
|
||||
|
||||
async fn index(State(state): State<AppState>) -> Result<Html<String>> {
|
||||
Ok(Html(
|
||||
state
|
||||
.templates
|
||||
.render("pages/index.html.jinja", context!())
|
||||
.await?,
|
||||
))
|
||||
}
|
|
@ -1,5 +1,4 @@
|
|||
use crate::templates::Templates;
|
||||
use crate::Besult;
|
||||
use crate::{prelude::*, templates::Templates};
|
||||
use std::sync::Arc;
|
||||
|
||||
#[derive(Clone)]
|
||||
|
@ -8,7 +7,7 @@ pub struct State {
|
|||
}
|
||||
|
||||
impl State {
|
||||
pub async fn try_new() -> Besult<Self> {
|
||||
pub async fn try_new() -> Result<Self> {
|
||||
let templates = Arc::new(Templates::try_load("src/templates").await?);
|
||||
|
||||
Ok(Self { templates })
|
||||
|
|
|
@ -1,24 +1,25 @@
|
|||
use crate::file_watcher::prelude::*;
|
||||
use crate::{
|
||||
file_watcher::{file_monitor, prelude::*},
|
||||
prelude::*,
|
||||
};
|
||||
|
||||
use crate::Besult;
|
||||
use axum::Router;
|
||||
use std::path::PathBuf;
|
||||
use std::str::FromStr;
|
||||
use std::sync::OnceLock;
|
||||
use tower_http::services::ServeDir;
|
||||
use tower_livereload::Reloader;
|
||||
use tracing::{info, instrument};
|
||||
|
||||
fn static_file_dir() -> &'static PathBuf {
|
||||
static STATIC_FILE_DIR: OnceLock<PathBuf> = OnceLock::new();
|
||||
STATIC_FILE_DIR.get_or_init(|| PathBuf::from_str("static").unwrap())
|
||||
}
|
||||
|
||||
#[instrument]
|
||||
pub fn router(reloader: Option<Reloader>) -> Besult<(Router, Option<FileWatcher>)> {
|
||||
pub fn router(reloader: Option<Reloader>) -> Result<(Router, Option<FileWatcher>)> {
|
||||
let watcher = if let Some(rl) = reloader {
|
||||
Some(file_watcher(static_file_dir(), move |ev| {
|
||||
info!("Static File Watcher Event: {ev:#?}");
|
||||
// TODO: debounce?
|
||||
Some(file_monitor(static_file_dir(), move || {
|
||||
info!("Static File Watcher Event");
|
||||
rl.reload()
|
||||
})?)
|
||||
} else {
|
||||
|
|
|
@ -1,22 +1,17 @@
|
|||
use std::{path::PathBuf, sync::Arc};
|
||||
|
||||
use crate::{file_watcher::prelude::*, prelude::*};
|
||||
use minijinja::Environment;
|
||||
use notify::{Config, RecommendedWatcher, RecursiveMode, Watcher};
|
||||
use pathdiff::diff_paths;
|
||||
use tokio::sync::{mpsc::channel, Mutex};
|
||||
use std::{path::PathBuf, sync::Arc};
|
||||
use tokio::sync::Mutex;
|
||||
use tower_livereload::Reloader;
|
||||
use tracing::{error, info};
|
||||
use tracing::{info, instrument};
|
||||
|
||||
use crate::{file_watcher::file_watcher, Besult};
|
||||
|
||||
#[derive(Clone)]
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Templates {
|
||||
env: Arc<Mutex<Environment<'static>>>,
|
||||
dir: PathBuf,
|
||||
}
|
||||
|
||||
pub type Error = Box<dyn std::error::Error>;
|
||||
|
||||
impl Templates {
|
||||
pub fn for_dir<P: Into<PathBuf>>(dir: P) -> Self {
|
||||
let env = Arc::new(Mutex::new(Environment::new()));
|
||||
|
@ -26,7 +21,7 @@ impl Templates {
|
|||
}
|
||||
}
|
||||
|
||||
pub async fn try_load<P: Into<PathBuf>>(dir: P) -> Result<Self, Error> {
|
||||
pub async fn try_load<P: Into<PathBuf>>(dir: P) -> Result<Self> {
|
||||
let result = Self::for_dir(dir);
|
||||
result.load_env().await?;
|
||||
Ok(result)
|
||||
|
@ -35,7 +30,7 @@ impl Templates {
|
|||
pub async fn start_watcher(
|
||||
self: Arc<Self>,
|
||||
reloader: Option<Reloader>,
|
||||
) -> Besult<Option<RecommendedWatcher>> {
|
||||
) -> Result<Option<FileWatcher>> {
|
||||
if let Some(rl) = reloader {
|
||||
Ok(Some(self.watch(rl).await?))
|
||||
} else {
|
||||
|
@ -43,28 +38,21 @@ impl Templates {
|
|||
}
|
||||
}
|
||||
|
||||
async fn watch(self: Arc<Self>, reloader: Reloader) -> Besult<RecommendedWatcher> {
|
||||
let (tx, mut rx) = channel(1);
|
||||
let watcher = file_watcher(self.dir.clone(), move |ev| {
|
||||
info!("Template File Watcher Event: {ev:#?}");
|
||||
match tx.blocking_send(ev) {
|
||||
Ok(_) => {}
|
||||
Err(err) => error!("Error sending template file watcher event: {err}"),
|
||||
};
|
||||
})?;
|
||||
let us = self.clone();
|
||||
tokio::spawn(async move {
|
||||
while let Some(_) = rx.recv().await {
|
||||
us.load_env()
|
||||
async fn watch(self: Arc<Self>, reloader: Reloader) -> Result<FileWatcher> {
|
||||
// TODO: only reload template that changed?
|
||||
let watcher = file_monitor(self.dir.clone(), move || {
|
||||
futures::executor::block_on(async {
|
||||
self.load_env()
|
||||
.await
|
||||
.expect("Failed to reload templates after template changed during runtime");
|
||||
reloader.reload()
|
||||
}
|
||||
reloader.reload();
|
||||
});
|
||||
})?;
|
||||
Ok(watcher)
|
||||
}
|
||||
|
||||
pub async fn load_env(&self) -> Result<(), Error> {
|
||||
#[instrument]
|
||||
pub async fn load_env(&self) -> Result<()> {
|
||||
info!("Loading templates...");
|
||||
for d in walkdir::WalkDir::new(&self.dir) {
|
||||
match d {
|
||||
|
@ -76,7 +64,7 @@ impl Templates {
|
|||
.unwrap()
|
||||
.to_string_lossy()
|
||||
.into_owned();
|
||||
info!("Loading template {filename:#?} ({d:#?})");
|
||||
info!("Loading template {filename:?} ({d:?})");
|
||||
self.env
|
||||
.lock()
|
||||
.await
|
||||
|
@ -85,7 +73,6 @@ impl Templates {
|
|||
Err(_) => todo!(),
|
||||
}
|
||||
}
|
||||
info!("Done loading templates!");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
@ -93,11 +80,12 @@ impl Templates {
|
|||
&self,
|
||||
template_name: &str,
|
||||
context: S,
|
||||
) -> Result<String, minijinja::Error> {
|
||||
self.env
|
||||
) -> Result<String> {
|
||||
Ok(self
|
||||
.env
|
||||
.lock()
|
||||
.await
|
||||
.get_template(template_name)?
|
||||
.render(context)
|
||||
.render(context)?)
|
||||
}
|
||||
}
|
||||
|
|
11
src/webserver.rs
Normal file
11
src/webserver.rs
Normal file
|
@ -0,0 +1,11 @@
|
|||
use crate::{prelude::*, tailwind};
|
||||
use axum::{serve, Router};
|
||||
|
||||
pub async fn webserver(router: Router) -> Result<()> {
|
||||
// TODO: only start tailwind if in dev mode?
|
||||
tokio::spawn(async move { tailwind::start_watcher() });
|
||||
|
||||
let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
|
||||
info!("Listening on {listener:?}");
|
||||
Ok(serve(listener, router).await?)
|
||||
}
|
Loading…
Reference in a new issue