feat(web): add users page

main
Jef Roosens 2025-06-24 13:49:37 +02:00
parent 4854c84601
commit 30609b1cef
No known key found for this signature in database
GPG Key ID: 21FD3D77D56BAF49
5 changed files with 102 additions and 0 deletions

View File

@ -77,6 +77,7 @@ pub fn router(ctx: Context) -> Router<Context> {
.route("/login", get(get_login).post(post_login))
.route("/logout", post(post_logout))
.merge(sessions::router(ctx.clone()))
.merge(users::router(ctx.clone()))
}
async fn get_index(

View File

@ -0,0 +1,47 @@
use axum::{
Extension, Router,
extract::{Path, Query, State},
http::HeaderMap,
routing::{delete, get},
};
use crate::{
server::{
Context,
error::{AppError, AppResult},
},
web::{Page, TemplateExt, TemplateResponse, ToQuery, View},
};
pub fn router(ctx: Context) -> Router<Context> {
Router::new()
.route("/users", get(get_users))
.route_layer(axum::middleware::from_fn_with_state(
ctx.clone(),
super::auth_web_middleware,
))
}
pub async fn get_users(
State(ctx): State<Context>,
headers: HeaderMap,
Extension(session): Extension<gpodder::Session>,
Query(page): Query<super::Pagination>,
) -> AppResult<TemplateResponse<Page<View>>> {
let next_page = page.next_page();
let user_id = session.user.id;
let users = tokio::task::spawn_blocking(move || {
ctx.store.admin(&session.user)?.paginated_users(page.into())
})
.await
.unwrap()?;
let next_page_query =
(users.len() == next_page.per_page as usize).then_some(next_page.to_query());
Ok(View::Users(users, user_id, next_page_query)
.page(&headers)
.headers(&headers)
.authenticated(true)
.response(&ctx.tera))
}

View File

@ -85,6 +85,10 @@ pub fn initialize_tera() -> tera::Result<tera::Tera> {
View::Sessions(Vec::new(), 0, None).template(),
include_str!("templates/views/sessions.html"),
),
(
View::Users(Vec::new(), 0, None).template(),
include_str!("templates/views/users.html"),
),
])?;
Ok(tera)

View File

@ -0,0 +1,21 @@
<h1>Users</h1>
<table>
<thead>
<th>Username</th>
</thead>
<tbody>
{% for user in users %}
<th>{{ user.username }}</th>
{% endfor %}
{%- if next_page_query %}
<tr
hx-get="/users?{{ next_page_query }}"
hx-trigger="revealed"
hx-swap="outerHTML"
hx-select="table > tbody > tr"
></tr>
{% endif %}
</tbody>
</table>

View File

@ -7,6 +7,7 @@ pub enum View {
Index,
Login,
Sessions(Vec<gpodder::Session>, i64, Option<Query>),
Users(Vec<gpodder::User>, i64, Option<Query>),
}
#[derive(Serialize)]
@ -16,12 +17,19 @@ struct Session {
last_seen: DateTime<Utc>,
}
#[derive(Serialize)]
struct User {
id: i64,
username: String,
}
impl Template for View {
fn template(&self) -> &'static str {
match self {
Self::Index => "views/index.html",
Self::Login => "views/login.html",
Self::Sessions(..) => "views/sessions.html",
Self::Users(..) => "views/users.html",
}
}
@ -41,6 +49,18 @@ impl Template for View {
ctx.insert("next_page_query", &query.encode());
}
}
Self::Users(users, current_user_id, query) => {
ctx.insert(
"users",
&users.into_iter().map(User::from).collect::<Vec<_>>(),
);
ctx.insert("current_user_id", &current_user_id);
if let Some(query) = query {
ctx.insert("next_page_query", &query.encode());
}
}
_ => {}
};
@ -57,3 +77,12 @@ impl From<gpodder::Session> for Session {
}
}
}
impl From<gpodder::User> for User {
fn from(value: gpodder::User) -> Self {
Self {
id: value.id,
username: value.username,
}
}
}