init
This commit is contained in:
commit
55e9cf1f9c
10 changed files with 1951 additions and 0 deletions
154
src/lib.rs
Normal file
154
src/lib.rs
Normal file
|
@ -0,0 +1,154 @@
|
|||
use std::{collections::HashSet, future::Future, net::SocketAddr, pin::Pin, sync::Arc};
|
||||
|
||||
use anyhow::{Context, Result};
|
||||
use askama::Template;
|
||||
use axum::{
|
||||
extract::{ConnectInfo, State},
|
||||
routing::get,
|
||||
Router,
|
||||
};
|
||||
use tokio::{fs::File, io::AsyncWriteExt, net::TcpListener, sync::RwLock};
|
||||
|
||||
async fn routes() -> Router {
|
||||
Router::new()
|
||||
.route("/", get(home))
|
||||
.with_state(AppState::load().await)
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct AppState {
|
||||
view_count: Arc<RwLock<u32>>,
|
||||
seen_ips: Arc<RwLock<HashSet<SocketAddr>>>,
|
||||
}
|
||||
|
||||
impl AppState {
|
||||
async fn load() -> Self {
|
||||
let view_count = match read_count().await {
|
||||
Ok(v) => v,
|
||||
Err(e) => {
|
||||
tracing::error!(message = "Failed to read count", error = e.to_string(),);
|
||||
0
|
||||
}
|
||||
};
|
||||
|
||||
let view_count = Arc::new(RwLock::new(view_count));
|
||||
let seen_ips = Arc::default();
|
||||
|
||||
Self {
|
||||
view_count,
|
||||
seen_ips,
|
||||
}
|
||||
}
|
||||
|
||||
async fn increment(&self, ip: SocketAddr) -> u32 {
|
||||
if self.seen_ips.read().await.contains(&ip) {
|
||||
let view_count = self.view_count.read().await;
|
||||
return *view_count;
|
||||
}
|
||||
self.seen_ips.write().await.insert(ip);
|
||||
|
||||
let mut view_count = self.view_count.write().await;
|
||||
*view_count += 1;
|
||||
|
||||
if let Err(e) = write_count(*view_count).await {
|
||||
tracing::error!(message = "Failed to write count", error = e.to_string(),);
|
||||
}
|
||||
|
||||
*view_count
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Template)]
|
||||
#[template(path = "index.html")]
|
||||
pub struct HomeTemplate {
|
||||
view_count: u32,
|
||||
}
|
||||
|
||||
fn state_path() -> anyhow::Result<std::path::PathBuf> {
|
||||
let project_dirs = directories::ProjectDirs::from("com", "lelgenio", "made-you-look")
|
||||
.context("building project dirs path")?;
|
||||
|
||||
let state_dir = project_dirs.state_dir().context("getting state dir")?;
|
||||
|
||||
Ok(state_dir.join("state"))
|
||||
}
|
||||
|
||||
async fn read_count() -> anyhow::Result<u32> {
|
||||
let file_path = state_path()?;
|
||||
let s = tokio::fs::read_to_string(file_path)
|
||||
.await
|
||||
.context("reading count")?;
|
||||
|
||||
s.parse().context("parsing count")
|
||||
}
|
||||
|
||||
async fn write_count(new_count: u32) -> anyhow::Result<()> {
|
||||
let file_path = state_path()?;
|
||||
|
||||
// let s = std::fs::read_to_string(file_path).context("reading count")?;
|
||||
tokio::fs::create_dir_all(file_path.parent().context("Getting state file dirname")?).await?;
|
||||
let mut file = File::create(file_path).await.context("Creating")?;
|
||||
|
||||
file.write(new_count.to_string().as_bytes())
|
||||
.await
|
||||
.context("Writting new count")?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[axum::debug_handler]
|
||||
pub async fn home(
|
||||
State(state): State<AppState>,
|
||||
ConnectInfo(ip): ConnectInfo<SocketAddr>,
|
||||
) -> HomeTemplate {
|
||||
tracing::info!(ip = ip.to_string());
|
||||
|
||||
let view_count = state.increment(ip).await;
|
||||
|
||||
HomeTemplate { view_count }
|
||||
}
|
||||
|
||||
pub struct Config {
|
||||
pub port: u16,
|
||||
}
|
||||
|
||||
pub struct RunningServer {
|
||||
pub port: u16,
|
||||
pub server: Pin<Box<dyn Future<Output = anyhow::Result<()>> + Send>>,
|
||||
}
|
||||
|
||||
pub async fn run(config: Config) -> Result<RunningServer> {
|
||||
setup_tracing();
|
||||
|
||||
let router = routes()
|
||||
.await
|
||||
.layer(tower_http::trace::TraceLayer::new_for_http())
|
||||
.into_make_service_with_connect_info::<SocketAddr>();
|
||||
|
||||
let tcp_listener = TcpListener::bind(format!("0.0.0.0:{}", config.port)).await?;
|
||||
|
||||
let port = tcp_listener.local_addr()?.port();
|
||||
|
||||
tracing::info!("Listening on http://localhost:{port}");
|
||||
let server = Box::pin(async move {
|
||||
axum::serve(tcp_listener, router).await?;
|
||||
Ok(())
|
||||
});
|
||||
|
||||
Ok(RunningServer { port, server })
|
||||
}
|
||||
|
||||
pub fn setup_tracing() {
|
||||
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};
|
||||
|
||||
let log_filter =
|
||||
std::env::var("MADE_YOU_LOOK_LOG").unwrap_or_else(|_| "made_you_look=debug,warn".into());
|
||||
|
||||
eprintln!("RUST_LOG: {log_filter}");
|
||||
|
||||
tracing_subscriber::registry()
|
||||
.with(tracing_subscriber::EnvFilter::new(log_filter))
|
||||
.with(tracing_subscriber::fmt::layer())
|
||||
.try_init()
|
||||
.ok();
|
||||
}
|
16
src/main.rs
Normal file
16
src/main.rs
Normal file
|
@ -0,0 +1,16 @@
|
|||
use anyhow::Result;
|
||||
use made_you_look::{run, Config};
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<()> {
|
||||
let config = Config {
|
||||
port: std::env::var("MADE_YOU_LOOK_SERVE_PORT")
|
||||
.ok()
|
||||
.and_then(|p| p.parse().ok())
|
||||
.unwrap_or(8000u16),
|
||||
};
|
||||
|
||||
run(config).await?.server.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue