diff --git a/Cargo.lock b/Cargo.lock index 0432eea..2908887 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -278,6 +278,8 @@ dependencies = [ "rmp-serde", "serde", "serde_json", + "strum 0.27.1", + "strum_macros", "time", "tokio", "tower-http", @@ -4146,7 +4148,7 @@ dependencies = [ "serde", "serde_json", "sqlx", - "strum", + "strum 0.26.3", "thiserror 1.0.69", "time", "tracing", @@ -4907,6 +4909,25 @@ version = "0.26.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06" +[[package]] +name = "strum" +version = "0.27.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f64def088c51c9510a8579e3c5d67c65349dcf755e5479ad3d010aa6454e2c32" + +[[package]] +name = "strum_macros" +version = "0.27.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c77a8c5abcaf0f9ce05d62342b7d298c346515365c36b673df4ebe3ced01fde8" +dependencies = [ + "heck 0.5.0", + "proc-macro2", + "quote", + "rustversion", + "syn 2.0.100", +] + [[package]] name = "subtle" version = "2.6.1" diff --git a/asklyphe-frontend/Cargo.toml b/asklyphe-frontend/Cargo.toml index 071dde5..d495bee 100644 --- a/asklyphe-frontend/Cargo.toml +++ b/asklyphe-frontend/Cargo.toml @@ -36,5 +36,7 @@ once_cell = "1.19.0" chrono = "0.4.33" rand = "0.8.5" url_encoded_data = "0.6.1" +strum = "0.27.1" +strum_macros = "0.27.1" env_logger = "*" diff --git a/asklyphe-frontend/src/routes/admin.rs b/asklyphe-frontend/src/routes/admin.rs index 3e97993..271d962 100644 --- a/asklyphe-frontend/src/routes/admin.rs +++ b/asklyphe-frontend/src/routes/admin.rs @@ -343,45 +343,14 @@ pub async fn admin_invitecode( } let active_codes = match list_invite_codes(nats.clone(), token.clone(), false).await { - Ok(mut v) => { - for v in &mut v { - if let Some(used_by) = &v.used_by { - if used_by.len() > 32 { - v.used_by = Some(format!("{}...", &used_by[0..32])); - } - } - - if v.creator.len() > 32 { - v.creator = format!("{}...", &v.creator[0..32]); - } - } - - v - }, + Ok(v) => v, Err(e) => { return e.into_response(); } }; let used_codes = match list_invite_codes(nats.clone(), token.clone(), true).await { - Ok(v) => v.into_iter().map(|mut v| { - if let Some(used_by) = &v.used_by { - if used_by.len() > 32 { - v.used_by = Some(format!("{}...", &used_by[0..32])); - } - } - - if v.creator.len() > 32 { - v.creator = format!("{}...", &v.creator[0..32]); - } - - if v.used_at.is_none() { - v.used_at = Some(String::from("unset")); - v - } else { - v - } - }).collect(), + Ok(v) => v.into_iter().map(|mut v| if v.used_at.is_none() { v.used_at = Some(String::from("unset")); v } else { v }).collect(), Err(e) => { return e.into_response(); } @@ -657,4 +626,4 @@ pub async fn admin_user_list( } else { Redirect::to("/").into_response() } -} \ No newline at end of file +} diff --git a/asklyphe-frontend/src/routes/announcement.rs b/asklyphe-frontend/src/routes/announcement.rs index 37865f6..3f0f082 100644 --- a/asklyphe-frontend/src/routes/announcement.rs +++ b/asklyphe-frontend/src/routes/announcement.rs @@ -27,6 +27,7 @@ use tracing::{debug, error}; use tracing::log::warn; use crate::{Opts, ALPHA, BUILT_ON, GIT_COMMIT, VERSION, YEAR}; use crate::routes::index::FrontpageAnnouncement; +use crate::routes::Themes; #[derive(Serialize, Debug)] struct FullAnnouncement { @@ -96,7 +97,7 @@ pub struct AnnouncementTemplate { built_on: String, year: String, alpha: bool, - theme: String, + theme: Themes, announcement: FullAnnouncement, } @@ -109,10 +110,10 @@ pub async fn announcement_full(Extension(nats): Extension FrontpageTemplate { year: YEAR.to_string(), alpha: ALPHA, count: WEBSITE_COUNT.load(Ordering::Relaxed), - theme: "default".to_string(), + theme: Themes::Default, announcement: None, } } @@ -185,7 +185,7 @@ pub struct FrontpageTemplate { year: String, alpha: bool, count: u64, - theme: String, + theme: Themes, announcement: Option, } @@ -202,7 +202,7 @@ pub async fn frontpage( year: YEAR.to_string(), alpha: ALPHA, count: WEBSITE_COUNT.load(Ordering::Relaxed), - theme: "default".to_string(), + theme: Themes::Default, announcement, } } @@ -217,7 +217,7 @@ struct IndexTemplate { year: String, alpha: bool, count: u64, - theme: String, + theme: Themes, announcement: Option, } @@ -234,7 +234,7 @@ pub async fn index( return (jar.remove("token"), frontpage_error(e.as_str(), opts.auth_url.clone())).into_response(); } }; - let theme = info.theme.clone(); + let theme = info.get_theme(); let announcement = latest_announcement(nats.clone()).await; @@ -260,7 +260,7 @@ pub async fn index( year: YEAR.to_string(), alpha: ALPHA, count: WEBSITE_COUNT.load(Ordering::Relaxed), - theme: "default".to_string(), + theme: Themes::Default, announcement, }.into_response() } diff --git a/asklyphe-frontend/src/routes/mod.rs b/asklyphe-frontend/src/routes/mod.rs index 445859b..75a22d1 100644 --- a/asklyphe-frontend/src/routes/mod.rs +++ b/asklyphe-frontend/src/routes/mod.rs @@ -10,7 +10,8 @@ * * You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ - +use std::fmt::Display; +use std::str::FromStr; use std::sync::Arc; use askama::Template; use askama_axum::IntoResponse; @@ -21,7 +22,13 @@ use asklyphe_common::nats::comms::ServiceResponse; use async_nats::jetstream; use axum::http::StatusCode; use serde::Serialize; -use tracing::error; +use strum::IntoEnumIterator; +use strum_macros::EnumIter; +use time::macros::utc_datetime; +use time::{OffsetDateTime, UtcDateTime}; +use tracing::{debug, error}; + +const RANDOM_THEME_EPOCH: UtcDateTime = utc_datetime!(2025-03-19 00:00); pub mod search; pub mod index; @@ -30,7 +37,102 @@ pub mod user_settings; pub mod admin; pub mod announcement; -#[derive(Serialize)] +#[derive(Default, EnumIter, PartialEq, Eq, Copy, Clone)] +pub enum Themes { + Classic, + Dark, + #[default] + Default, + Freaky, + Gloss, + Oled, + Water, + Random +} + +impl Themes { + pub fn get_all_themes() -> Vec { + Self::iter().collect() + } + + pub fn display_name(&self) -> String { + match self { + Themes::Classic => { + "classic".to_string() + } + Themes::Dark => { + "dark theme".to_string() + } + Themes::Default => { + "default theme".to_string() + } + Themes::Freaky => { + "freaky".to_string() + } + Themes::Gloss => { + "gloss".to_string() + } + Themes::Oled => { + "lights out".to_string() + } + Themes::Water => { + "water".to_string() + } + Themes::Random => { + "random".to_string() + } + } + } + + pub fn internal_name(&self) -> String { + match self { + Themes::Classic => { + "classic".to_string() + } + Themes::Dark => { + "dark".to_string() + } + Themes::Default => { + "default".to_string() + } + Themes::Freaky => { + "freaky".to_string() + } + Themes::Gloss => { + "gloss".to_string() + } + Themes::Oled => { + "oled".to_string() + } + Themes::Water => { + "water".to_string() + } + Themes::Random => { + "random".to_string() + } + } + } +} + +impl FromStr for Themes { + type Err = (); + + fn from_str(s: &str) -> Result { + match s { + "classic" => Ok(Themes::Classic), + "dark" => Ok(Themes::Dark), + "default" => Ok(Themes::Default), + "freaky" => Ok(Themes::Freaky), + "gloss" => Ok(Themes::Gloss), + "oled" => Ok(Themes::Oled), + "water" => Ok(Themes::Water), + "random" => Ok(Themes::Random), + _ => Err(()) + } + } +} + +#[derive(Serialize, Clone)] pub struct UserInfo { pub username: String, pub email: String, @@ -39,6 +141,27 @@ pub struct UserInfo { pub administrator: bool, } +impl UserInfo { + pub fn get_theme(&self) -> Themes { + let theme: Themes = self.theme.parse().unwrap_or_default(); + + if theme.eq(&Themes::Random) { + let possible_themes = Themes::get_all_themes(); + let current_day = UtcDateTime::now(); + + let rand_value = (((current_day - RANDOM_THEME_EPOCH).as_seconds_f64() / 86400.0) % possible_themes.len() as f64) as usize; + + *possible_themes.get(rand_value).unwrap_or(&Themes::Default) + } else { + theme + } + } + + pub fn get_true_theme(&self) -> Themes { + self.theme.parse().unwrap() + } +} + pub async fn authenticate_user(nats: Arc, token: String) -> Result { let response = comms::query_service( comms::Query::AuthService(AuthServiceQuery { @@ -114,4 +237,4 @@ pub struct NotFoundTemplate; pub async fn not_found() -> impl IntoResponse { (StatusCode::NOT_FOUND, NotFoundTemplate).into_response() -} \ No newline at end of file +} diff --git a/asklyphe-frontend/src/routes/search.rs b/asklyphe-frontend/src/routes/search.rs index 0c90393..f1d43b7 100644 --- a/asklyphe-frontend/src/routes/search.rs +++ b/asklyphe-frontend/src/routes/search.rs @@ -12,7 +12,7 @@ */ use crate::routes::index::frontpage_error; -use crate::routes::{authenticate_user, UserInfo}; +use crate::routes::{authenticate_user, Themes, UserInfo}; use crate::searchbot::{gather_image_results, gather_search_results}; use crate::unit_converter; use crate::unit_converter::UnitConversion; @@ -111,7 +111,7 @@ struct SearchTemplateJavascript { built_on: String, year: String, alpha: bool, - theme: String, + theme: Themes, } pub async fn search_js( @@ -121,7 +121,7 @@ pub async fn search_js( Extension(opts): Extension, ) -> impl IntoResponse { fn error_response(query: String, info: UserInfo, error: &str) -> SearchTemplateJavascript { - let theme = info.theme.clone(); + let theme = info.get_theme(); let querystr = url_encoded_data::stringify(&[("q", query.as_str())]); SearchTemplateJavascript { info, @@ -175,7 +175,7 @@ pub async fn search_js( query = query.replace("-complications", ""); } - let theme = info.theme.clone(); + let theme = info.get_theme(); let querystr = url_encoded_data::stringify(&[("q", og_query.as_str())]); SearchTemplateJavascript { info, @@ -217,7 +217,7 @@ pub struct SearchTemplate { pub built_on: String, pub year: String, pub alpha: bool, - pub theme: String, + pub theme: Themes, } pub async fn search_nojs( @@ -227,7 +227,7 @@ pub async fn search_nojs( Extension(opts): Extension, ) -> impl IntoResponse { fn error_response(query: String, info: UserInfo, error: &str) -> SearchTemplate { - let theme = info.theme.clone(); + let theme = info.get_theme(); let querystr = url_encoded_data::stringify(&[("q", query.as_str())]); SearchTemplate { info, @@ -416,7 +416,7 @@ pub struct ImageSearchTemplate { pub built_on: String, pub year: String, pub alpha: bool, - pub theme: String, + pub theme: Themes, } pub async fn image_search( jar: CookieJar, @@ -425,7 +425,7 @@ pub async fn image_search( Extension(opts): Extension, ) -> impl IntoResponse { fn error_response(query: String, info: UserInfo, error: &str) -> ImageSearchTemplate { - let theme = info.theme.clone(); + let theme = info.get_theme(); let querystr = url_encoded_data::stringify(&[("q", query.as_str())]); ImageSearchTemplate { info, diff --git a/asklyphe-frontend/src/routes/user_settings.rs b/asklyphe-frontend/src/routes/user_settings.rs index df53dee..0a022c6 100644 --- a/asklyphe-frontend/src/routes/user_settings.rs +++ b/asklyphe-frontend/src/routes/user_settings.rs @@ -27,49 +27,12 @@ use serde::Deserialize; use tokio::sync::Mutex; use tracing::error; use crate::{BUILT_ON, GIT_COMMIT, Opts, ALPHA, VERSION, WEBSITE_COUNT, YEAR}; -use crate::routes::{authenticate_user, UserInfo}; - -pub struct Theme<'a> { - pub value: &'a str, - pub name: &'a str, -} - -pub static THEMES: &[Theme] = &[ - Theme { - value: "default", - name: "default theme", - }, - Theme { - value: "dark", - name: "dark theme", - }, - Theme { - value: "oled", - name: "lights out", - }, - Theme { - value: "classic", - name: "classic", - }, - Theme { - value: "freaky", - name: "freaky", - }, - Theme { - value: "water", - name: "water", - }, - Theme { - value: "gloss", - name: "gloss", - }, -]; +use crate::routes::{authenticate_user, Themes, UserInfo}; #[derive(Template)] #[template(path = "user_settings.html")] pub struct SettingsTemplate { - themes: &'static [Theme<'static>], - + themes: Vec, error: Option, info: UserInfo, @@ -80,7 +43,8 @@ pub struct SettingsTemplate { year: String, alpha: bool, count: u64, - theme: String, + theme: Themes, + true_theme: Themes, } pub async fn user_settings( @@ -96,11 +60,11 @@ pub async fn user_settings( return (jar.remove("token"), crate::routes::index::frontpage_error(e.as_str(), opts.auth_url.clone())).into_response(); } }; - let theme = info.theme.clone(); + let theme = info.get_theme(); SettingsTemplate { - themes: THEMES, + themes: Themes::get_all_themes(), error: None, - info, + info: info.clone(), search_query: "".to_string(), version: VERSION.to_string(), git_commit: GIT_COMMIT.to_string(), @@ -109,6 +73,7 @@ pub async fn user_settings( alpha: ALPHA, count: WEBSITE_COUNT.load(Ordering::Relaxed), theme, + true_theme: info.get_true_theme(), }.into_response() } else { Redirect::temporary("/").into_response() @@ -126,11 +91,11 @@ pub async fn theme_change_post( Extension(opts): Extension, Form(input): Form, ) -> impl IntoResponse { - fn settings_error(info: UserInfo, theme: String, error: String) -> impl IntoResponse { + fn settings_error(info: UserInfo, theme: Themes, error: String) -> impl IntoResponse { SettingsTemplate { - themes: THEMES, + themes: Themes::get_all_themes(), error: Some(error), - info, + info: info.clone(), search_query: "".to_string(), version: VERSION.to_string(), git_commit: GIT_COMMIT.to_string(), @@ -139,6 +104,7 @@ pub async fn theme_change_post( alpha: ALPHA, count: WEBSITE_COUNT.load(Ordering::Relaxed), theme, + true_theme: info.get_true_theme(), }.into_response() } @@ -151,10 +117,12 @@ pub async fn theme_change_post( } }; - let theme = info.theme.clone(); - - if !THEMES.iter().map(|v| v.value.to_string()).collect::>().contains(&input.theme.clone().unwrap_or("default".to_string())) { - return settings_error(info, theme, "invalid input, please try again!".to_string()).into_response(); + let theme = info.get_theme(); + + if let Some(theme_input) = &input.theme { + if !Themes::get_all_themes().iter().map(|x| x.internal_name().to_string()).collect::>().contains(&theme_input) { + return settings_error(info, theme.clone(), "invalid input, please try again!".to_string()).into_response(); + } } let response = comms::query_service(comms::Query::AuthService(AuthServiceQuery { @@ -200,4 +168,4 @@ pub async fn theme_change_post( } else { Redirect::to("/").into_response() } -} \ No newline at end of file +} diff --git a/asklyphe-frontend/src/searchbot.rs b/asklyphe-frontend/src/searchbot.rs index ca43ef9..2f60780 100644 --- a/asklyphe-frontend/src/searchbot.rs +++ b/asklyphe-frontend/src/searchbot.rs @@ -341,7 +341,7 @@ pub async fn gather_search_results(nats: Arc, query: &str, u search_results.remove(rm - i); } - let theme = user_info.theme.clone(); + let theme = user_info.get_theme(); let querystr = url_encoded_data::stringify(&[("q", query)]); SearchTemplate { info: user_info, @@ -489,7 +489,7 @@ pub async fn gather_image_results(nats: Arc, query: &str, us result.src = format!("/imgproxy?{}", url); } - let theme = user_info.theme.clone(); + let theme = user_info.get_theme(); ImageSearchTemplate { info: user_info, error: None, diff --git a/asklyphe-frontend/templates/frontpage.html b/asklyphe-frontend/templates/frontpage.html index 7b84662..7536376 100644 --- a/asklyphe-frontend/templates/frontpage.html +++ b/asklyphe-frontend/templates/frontpage.html @@ -3,8 +3,8 @@ {% block title %}the best search engine{% endblock %} {% block head %} - - + + {% endblock %} {% block page %} diff --git a/asklyphe-frontend/templates/home.html b/asklyphe-frontend/templates/home.html index 0d6296d..44e9eb0 100644 --- a/asklyphe-frontend/templates/home.html +++ b/asklyphe-frontend/templates/home.html @@ -4,8 +4,8 @@ {% block head %} - - + + {% endblock %} {% block page %} @@ -71,4 +71,4 @@ {% include "ui/footer.html" %} -{% endblock %} \ No newline at end of file +{% endblock %} diff --git a/asklyphe-frontend/templates/image_search.html b/asklyphe-frontend/templates/image_search.html index 0adb67c..de06850 100644 --- a/asklyphe-frontend/templates/image_search.html +++ b/asklyphe-frontend/templates/image_search.html @@ -4,7 +4,7 @@ {% block head %} - + {% if search_query == "notnite" %}{% endif %} {% endblock %} @@ -61,4 +61,4 @@ {% include "ui/footer.html" %} -{% endblock %} \ No newline at end of file +{% endblock %} diff --git a/asklyphe-frontend/templates/search.html b/asklyphe-frontend/templates/search.html index db6868b..82d1dcc 100644 --- a/asklyphe-frontend/templates/search.html +++ b/asklyphe-frontend/templates/search.html @@ -4,7 +4,7 @@ {% block head %} - + {% if search_query == "notnite" %}{% endif %} {% endblock %} @@ -97,4 +97,4 @@ {% include "ui/footer.html" %} -{% endblock %} \ No newline at end of file +{% endblock %} diff --git a/asklyphe-frontend/templates/search_js.html b/asklyphe-frontend/templates/search_js.html index aa55755..71cc5d3 100644 --- a/asklyphe-frontend/templates/search_js.html +++ b/asklyphe-frontend/templates/search_js.html @@ -4,7 +4,7 @@ {% block head %} - + {% if search_query == "notnite" %}{% endif %} {% endblock %} @@ -76,4 +76,4 @@ {% include "ui/footer.html" %} -{% endblock %} \ No newline at end of file +{% endblock %} diff --git a/asklyphe-frontend/templates/shell.html b/asklyphe-frontend/templates/shell.html index aa1802e..a61cdd9 100644 --- a/asklyphe-frontend/templates/shell.html +++ b/asklyphe-frontend/templates/shell.html @@ -12,11 +12,11 @@ href="/static/osd.xml" /> - + {% block head %}{% endblock %} {% block page %}{% endblock %} - \ No newline at end of file + diff --git a/asklyphe-frontend/templates/user_settings.html b/asklyphe-frontend/templates/user_settings.html index c09a8f8..63cfec8 100644 --- a/asklyphe-frontend/templates/user_settings.html +++ b/asklyphe-frontend/templates/user_settings.html @@ -4,7 +4,7 @@ {% block head %} - + {% endblock %} {% block page %} @@ -43,16 +43,15 @@

theme

- {% for t in themes %} - {%if theme==t.value%} -

your current theme is: "{{t.name}}"

- {%endif%} - {% endfor %} +

your current theme is: "{{true_theme.display_name()}}"

+ {% if true_theme.internal_name() != theme.internal_name() %} +

today's random theme is {{ theme.display_name() }}

+ {% endif %}
@@ -65,4 +64,4 @@ {% include "ui/footer.html" %}
-{% endblock %} \ No newline at end of file +{% endblock %} diff --git a/authservice/src/db/user.rs b/authservice/src/db/user.rs index 1f6ce8a..989b58d 100644 --- a/authservice/src/db/user.rs +++ b/authservice/src/db/user.rs @@ -457,6 +457,7 @@ pub async fn user_count(db: &DatabaseConnection) -> Result Result { // dont fucking touch this, i don't know why it works but it does, it's actually evil + // note: doesn't work Ok(user::Entity::find().filter(user::Column::Flags.into_expr().binary(BinOper::LShift, Expr::value(63 - 2)).lt(1 << (63 - 2))) .count(db).await.map_err(|e| { error!("DATABASE ERROR WHILE ADMINCOUNT: {e}"); @@ -482,4 +483,4 @@ pub async fn email_list(db: &DatabaseConnection) -> Result })?.into_iter().map(|v| { (v.username, v.email) }).collect()) -} \ No newline at end of file +}