proper layout

This commit is contained in:
Priec
2026-05-17 16:30:34 +02:00
parent 046f7c04c8
commit 0a36e8839c
3 changed files with 85 additions and 9 deletions

View File

@@ -63,7 +63,14 @@
<a href="/" class="btn btn-ghost btn-sm">Home</a> <a href="/" class="btn btn-ghost btn-sm">Home</a>
<a href="/about" class="btn btn-ghost btn-sm">About</a> <a href="/about" class="btn btn-ghost btn-sm">About</a>
<a href="/blog" class="btn btn-ghost btn-sm">Blog</a> <a href="/blog" class="btn btn-ghost btn-sm">Blog</a>
{% if logged_in_admin %}
<a href="/admin/dashboard" class="btn btn-ghost btn-sm">Dashboard</a>
<form method="post" action="/admin/logout">
<button type="submit" class="btn btn-ghost btn-sm">Logout</button>
</form>
{% else %}
<a href="/admin/login" class="btn btn-ghost btn-sm">Admin</a> <a href="/admin/login" class="btn btn-ghost btn-sm">Admin</a>
{% endif %}
</div> </div>
<div class="flex items-center gap-1"> <div class="flex items-center gap-1">
<div class="dropdown dropdown-end md:hidden"> <div class="dropdown dropdown-end md:hidden">
@@ -78,7 +85,14 @@
<a href="/" class="btn btn-ghost btn-sm justify-start">Home</a> <a href="/" class="btn btn-ghost btn-sm justify-start">Home</a>
<a href="/about" class="btn btn-ghost btn-sm justify-start">About</a> <a href="/about" class="btn btn-ghost btn-sm justify-start">About</a>
<a href="/blog" class="btn btn-ghost btn-sm justify-start">Blog</a> <a href="/blog" class="btn btn-ghost btn-sm justify-start">Blog</a>
{% if logged_in_admin %}
<a href="/admin/dashboard" class="btn btn-ghost btn-sm justify-start">Dashboard</a>
<form method="post" action="/admin/logout">
<button type="submit" class="btn btn-ghost btn-sm w-full justify-start">Logout</button>
</form>
{% else %}
<a href="/admin/login" class="btn btn-ghost btn-sm justify-start">Admin</a> <a href="/admin/login" class="btn btn-ghost btn-sm justify-start">Admin</a>
{% endif %}
</div> </div>
</div> </div>
<div class="dropdown dropdown-end"> <div class="dropdown dropdown-end">

View File

@@ -14,7 +14,7 @@ use std::sync::OnceLock;
use time::Duration as TimeDuration; use time::Duration as TimeDuration;
pub static EMAIL_DOMAIN_RE: OnceLock<Regex> = OnceLock::new(); pub static EMAIL_DOMAIN_RE: OnceLock<Regex> = OnceLock::new();
const AUTH_COOKIE: &str = "auth_token"; pub(crate) const AUTH_COOKIE: &str = "auth_token";
fn get_allow_email_domain_re() -> &'static Regex { fn get_allow_email_domain_re() -> &'static Regex {
EMAIL_DOMAIN_RE.get_or_init(|| { EMAIL_DOMAIN_RE.get_or_init(|| {

View File

@@ -5,6 +5,7 @@ use crate::{
users::{self, LoginParams}, users::{self, LoginParams},
}, },
}; };
use axum_extra::extract::cookie::CookieJar;
use chrono::Utc; use chrono::Utc;
use loco_rs::prelude::*; use loco_rs::prelude::*;
use sea_orm::{ use sea_orm::{
@@ -87,8 +88,27 @@ async fn article_by_id(ctx: &AppContext, id: Uuid) -> Result<blog_articles::Mode
.ok_or_else(|| Error::NotFound) .ok_or_else(|| Error::NotFound)
} }
async fn logged_in_admin(ctx: &AppContext, jar: &CookieJar) -> bool {
let Some(cookie) = jar.get(auth_controller::AUTH_COOKIE) else {
return false;
};
let Ok(jwt_config) = ctx.config.get_jwt_config() else {
return false;
};
let Ok(claims) = loco_rs::auth::jwt::JWT::new(&jwt_config.secret).validate(cookie.value())
else {
return false;
};
let Ok(user) = users::Model::find_by_pid(&ctx.db, &claims.claims.pid).await else {
return false;
};
admin::is_admin(ctx, &user)
}
#[debug_handler] #[debug_handler]
async fn home( async fn home(
jar: CookieJar,
ViewEngine(v): ViewEngine<TeraView>, ViewEngine(v): ViewEngine<TeraView>,
State(ctx): State<AppContext>, State(ctx): State<AppContext>,
) -> Result<Response> { ) -> Result<Response> {
@@ -99,19 +119,32 @@ async fn home(
.all(&ctx.db) .all(&ctx.db)
.await?; .await?;
format::view(&v, "home/index.html", json!({ "articles": articles })) format::view(
&v,
"home/index.html",
json!({ "articles": articles, "logged_in_admin": logged_in_admin(&ctx, &jar).await }),
)
} }
#[debug_handler] #[debug_handler]
async fn about( async fn about(
jar: CookieJar,
ViewEngine(v): ViewEngine<TeraView>, ViewEngine(v): ViewEngine<TeraView>,
State(ctx): State<AppContext>, State(ctx): State<AppContext>,
) -> Result<Response> { ) -> Result<Response> {
format::view(&v, "pages/about.html", json!({ "page": about_page(&ctx).await? })) format::view(
&v,
"pages/about.html",
json!({
"page": about_page(&ctx).await?,
"logged_in_admin": logged_in_admin(&ctx, &jar).await,
}),
)
} }
#[debug_handler] #[debug_handler]
async fn blog_index( async fn blog_index(
jar: CookieJar,
ViewEngine(v): ViewEngine<TeraView>, ViewEngine(v): ViewEngine<TeraView>,
State(ctx): State<AppContext>, State(ctx): State<AppContext>,
) -> Result<Response> { ) -> Result<Response> {
@@ -121,11 +154,16 @@ async fn blog_index(
.all(&ctx.db) .all(&ctx.db)
.await?; .await?;
format::view(&v, "blog/index.html", json!({ "articles": articles })) format::view(
&v,
"blog/index.html",
json!({ "articles": articles, "logged_in_admin": logged_in_admin(&ctx, &jar).await }),
)
} }
#[debug_handler] #[debug_handler]
async fn blog_show( async fn blog_show(
jar: CookieJar,
ViewEngine(v): ViewEngine<TeraView>, ViewEngine(v): ViewEngine<TeraView>,
Path(slug): Path<String>, Path(slug): Path<String>,
State(ctx): State<AppContext>, State(ctx): State<AppContext>,
@@ -142,12 +180,28 @@ async fn blog_show(
active.view_count = Set(next_count); active.view_count = Set(next_count);
let article = active.update(&ctx.db).await?; let article = active.update(&ctx.db).await?;
format::view(&v, "blog/show.html", json!({ "article": article })) format::view(
&v,
"blog/show.html",
json!({ "article": article, "logged_in_admin": logged_in_admin(&ctx, &jar).await }),
)
} }
#[debug_handler] #[debug_handler]
async fn admin_login_page(ViewEngine(v): ViewEngine<TeraView>) -> Result<Response> { async fn admin_login_page(
format::view(&v, "admin/login.html", json!({ "error": null })) jar: CookieJar,
ViewEngine(v): ViewEngine<TeraView>,
State(ctx): State<AppContext>,
) -> Result<Response> {
if logged_in_admin(&ctx, &jar).await {
return format::redirect("/admin/dashboard");
}
format::view(
&v,
"admin/login.html",
json!({ "error": null, "logged_in_admin": false }),
)
} }
#[debug_handler] #[debug_handler]
@@ -157,11 +211,19 @@ async fn admin_login(
Form(params): Form<LoginParams>, Form(params): Form<LoginParams>,
) -> Result<Response> { ) -> Result<Response> {
let Ok(user) = users::Model::find_by_email(&ctx.db, &params.email).await else { let Ok(user) = users::Model::find_by_email(&ctx.db, &params.email).await else {
return format::view(&v, "admin/login.html", json!({ "error": "Invalid credentials" })); return format::view(
&v,
"admin/login.html",
json!({ "error": "Invalid credentials", "logged_in_admin": false }),
);
}; };
if !user.verify_password(&params.password) || !admin::is_admin(&ctx, &user) { if !user.verify_password(&params.password) || !admin::is_admin(&ctx, &user) {
return format::view(&v, "admin/login.html", json!({ "error": "Invalid credentials" })); return format::view(
&v,
"admin/login.html",
json!({ "error": "Invalid credentials", "logged_in_admin": false }),
);
} }
let jwt_secret = ctx.config.get_jwt_config()?; let jwt_secret = ctx.config.get_jwt_config()?;