use crate::model::client::Client; use axum::{http::HeaderMap, routing::get, Router}; use axum_extra::extract::{cookie::Cookie, CookieJar}; use sqlx::{pool::PoolOptions, sqlite::SqliteConnectOptions, SqlitePool}; use std::{fmt::Display, str::FromStr, sync::Arc}; use tower_http::services::ServeDir; use uuid::Uuid; #[macro_use] extern crate rust_i18n; i18n!("locales", fallback = "en"); mod game; mod index; pub(crate) mod language; pub(crate) mod model; mod page; pub(crate) mod random_names; pub(crate) enum Backend { Sqlite(SqlitePool), } #[derive(Debug)] enum Language { German, English, } impl Display for Language { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Language::German => f.write_str("de"), Language::English => f.write_str("en"), } } } impl Language { pub(crate) fn next_language(&self) -> Self { match self { Language::German => Language::English, Language::English => Language::German, } } fn to_locale(&self) -> &'static str { match self { Language::German => "de", Language::English => "en", } } } impl From for Language { fn from(value: String) -> Self { if value.starts_with("de") { Language::German } else { Language::English } } } struct Req { client: Client, lang: Language, } impl Backend { async fn client(&self, cookies: CookieJar) -> (CookieJar, Client) { let existing_uuid = cookies .get("client_id") .and_then(|cookie| Uuid::parse_str(cookie.value()).ok()); match existing_uuid { Some(uuid) => (cookies, self.get_client(&uuid).await), None => { let new_id = Uuid::new_v4(); let updated_cookies = cookies.add(Cookie::new("client_id", new_id.to_string())); (updated_cookies, self.get_client(&new_id).await) } } } // Combined method for getting both client and language async fn client_full(&self, cookies: CookieJar, headers: &HeaderMap) -> (CookieJar, Req) { let (cookies, client) = self.client(cookies).await; let lang = language::language(&cookies, headers); (cookies, Req { client, lang }) } async fn set_client_name(&self, client: &Client, name: &str) -> Result<(), String> { if name.len() > 25 { return Err("Maximum 25 chars are allowed".into()); } if name.len() < 3 { return Err("Minimum of 3 chars needed".into()); } match self { Backend::Sqlite(db) => { sqlx::query!( "UPDATE client SET name = ? WHERE uuid = ?;", name, client.uuid ) .execute(db) .await .unwrap(); } } Ok(()) } } #[tokio::main] async fn main() { let connection_options = SqliteConnectOptions::from_str("sqlite://db.sqlite").unwrap(); let db: SqlitePool = PoolOptions::new() .connect_with(connection_options) .await .unwrap(); let app = Router::new() .route("/", get(index::index)) .nest_service("/static", ServeDir::new("./static/serve")) .merge(game::routes()) .with_state(Arc::new(Backend::Sqlite(db))); // run our app with hyper, listening globally on port 3000 let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap(); axum::serve(listener, app).await.unwrap(); }