Files
aef-website/src/main.rs
2025-08-03 10:36:08 +02:00

137 lines
3.6 KiB
Rust

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<String> 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();
}