Fix loading of profiles from user administration screen

This commit is contained in:
Chris Jean-Marie 2024-09-30 00:15:25 +00:00
parent 8905d05b01
commit 4f152df006
7 changed files with 226 additions and 129 deletions

View File

@ -51,8 +51,7 @@ pub struct AppState {
pub db_pool: SqlitePool,
}
#[derive(Clone, Debug, FromRow)]
#[derive(Serialize, Deserialize)]
#[derive(Default, Clone, Debug, FromRow, Serialize, Deserialize)]
pub struct UserData {
#[allow(dead_code)]
pub id: i64,
@ -60,12 +59,7 @@ pub struct UserData {
pub name: String,
pub family_name: String,
pub given_name: String,
/* pub discriminator: String,
pub avatar: String,
pub access_token: String,
pub refresh_token: String,
pub expires_at: i64,
*/}
}
#[tokio::main]
async fn main() {
@ -93,6 +87,7 @@ async fn main() {
//Routes that require authentication
.route("/profile", get(profile))
.route("/useradmin", get(useradmin))
.route("/users/:user_id", get(user_profile))
.route_layer(middleware::from_fn_with_state(app_state.db_pool.clone(), check_auth))
//Routes that don't require authentication

View File

@ -1,3 +1,5 @@
use std::path::Path;
use super::{AppError, UserData};
use axum::{
body::Body,
@ -43,7 +45,11 @@ pub async fn inject_user_data(
let id = query.0;
let expires_at = query.1;
if expires_at > Utc::now().timestamp() {
let row = sqlx::query_as!(UserData, "SELECT * FROM users WHERE id = ?", id)
let row = sqlx::query_as!(
UserData,
"SELECT * FROM users WHERE id = ?",
id
)
.fetch_one(&db_pool)
.await?;
@ -65,8 +71,11 @@ pub async fn inject_user_data(
Ok(next.run(request).await)
}
pub async fn check_auth(State(app_state): State<SqlitePool>, request: Request<Body>, next: Next) -> Result<impl IntoResponse, AppError> {
pub async fn check_auth(
State(app_state): State<SqlitePool>,
request: Request<Body>,
next: Next,
) -> Result<impl IntoResponse, AppError> {
if request
.extensions()
.get::<Option<UserData>>()
@ -75,19 +84,60 @@ pub async fn check_auth(State(app_state): State<SqlitePool>, request: Request<Bo
{
let path = &*request.uri().to_string();
println!("{}", path);
println!("{}",&*request.extensions().get::<Option<UserData>>().unwrap().as_ref().unwrap().email);
println!(
"{}",
&*request
.extensions()
.get::<Option<UserData>>()
.unwrap()
.as_ref()
.unwrap()
.email
);
let query: Result<(i64,), _> = match path {
"/profile" =>
"/profile" => {
sqlx::query_as(r#"select u.id from users u where email =?"#)
.bind(
request
.extensions()
.get::<Option<UserData>>()
.unwrap()
.as_ref()
.unwrap()
.email
.as_str(),
)
.fetch_one(&app_state)
.await
}
_ => {
// loop through path to find a permission
let mut remaining_path = Path::new(path);
loop {
let query: Result<(String,), _> = sqlx::query_as(r#"select r.item from role_permissions r join user_roles ur on ur.role_id = r.role_id join users u on u.id = ur.user_id where item = ? and email =?"#)
.bind(remaining_path.to_str().unwrap())
.bind(request.extensions().get::<Option<UserData>>().unwrap().as_ref().unwrap().email.as_str())
.fetch_one(&app_state)
.await,
_ => sqlx::query_as(r#"select u.id from role_permissions r join user_roles ur on ur.role_id = r.role_id join users u on u.id = ur.user_id where item = ? and email =?"#)
.bind(&*request.uri().to_string())
.await;
if let Ok(query) = query {
if query.0 != "" {
break;
}
}
if remaining_path.parent().is_none() {
break;
}
remaining_path = remaining_path.parent().unwrap();
println!("{}", remaining_path.to_str().unwrap());
}
sqlx::query_as(r#"select u.id from role_permissions r join user_roles ur on ur.role_id = r.role_id join users u on u.id = ur.user_id where item = ? and email =?"#)
.bind(remaining_path.to_str().unwrap())
.bind(request.extensions().get::<Option<UserData>>().unwrap().as_ref().unwrap().email.as_str())
.fetch_one(&app_state)
.await,
.await
}
};
// user is logged in

View File

@ -1,5 +1,5 @@
use askama_axum::Template;
use axum::{extract::State, response::IntoResponse, Extension};
use axum::{extract::{Path, State}, response::IntoResponse, Extension};
use http::Request;
use sqlx::SqlitePool;
@ -10,24 +10,51 @@ use crate::{HtmlTemplate, UserData};
struct ProfileTemplate {
logged_in: bool,
name: String,
family_name: String,
given_name: String,
email: String,
user: UserData
}
/// Handles the profile page.
pub async fn profile<T>(
Extension(user_data): Extension<Option<UserData>>,
_request: Request<T>,
) -> impl IntoResponse {
// Extract the user's name from the user data.
let user_name = user_data.as_ref().map(|s| s.name.clone());
let logged_in = user_data.is_some();
let name = user_name.unwrap_or_default();
let Some(UserData {id, email, family_name, given_name, name}) = user_data
else {
return HtmlTemplate(ProfileTemplate { logged_in: false, name: "".to_owned(), given_name: "".to_owned(), family_name: "".to_owned(), email: "".to_owned() });
};
let logged_in = id > 0;
// Extract the user data.
let user = user_data.as_ref().unwrap();
let template = ProfileTemplate { logged_in, name, given_name, family_name, email };
HtmlTemplate(template)
// Create the profile template.
let template = ProfileTemplate { logged_in, name, user: user.clone() };
return HtmlTemplate(template)
}
pub async fn user_profile<T>(
Path(user_id): Path<i64>,
State(db_pool): State<SqlitePool>,
Extension(user_data): Extension<Option<UserData>>,
_request: Request<T>,
) -> impl IntoResponse {
// Extract the user's name from the user data.
let user_name = user_data.as_ref().map(|s| s.name.clone());
let logged_in = user_data.is_some();
let name = user_name.unwrap_or_default();
// Extract the user data.
let user = sqlx::query_as!(
UserData,
"SELECT * FROM users WHERE id = ?",
user_id
)
.fetch_one(&db_pool)
.await
.unwrap();
// Create the profile template.
let template = ProfileTemplate { logged_in, name, user: user };
return HtmlTemplate(template)
}
#[derive(Template)]

View File

@ -10,12 +10,13 @@
<meta name="author" content="">
<!-- Bootstrap CSS -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet"
integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">
<body>
<nav class="navbar navbar-expand-sm bg-light">
<div class="container-fluid">
<div class="row fixed-top sticky-top">
<nav class="navbar navbar-expand-sm bg-light">
<!-- Links -->
<ul class="navbar-nav">
<li class="nav-item">
@ -34,26 +35,25 @@
<li class="nav-item"><a class="nav-link" href="/login">Login</a></li>
{% endif %}
</ul>
</div>
</nav>
</div>
<div class="row flex-grow-1">
{% block content %}{% endblock content %}
<div class="container marketing">
</div>
<div class="row fixed-bottom sticky-bottom">
<div class="container-fluid text-center bg-light">
<!-- FOOTER -->
<footer>
<p class="pull-right"><a href="#">Back to top</a></p>
<p>© 2024 Jean-Marie family</p>
</footer>
</div><!-- /.container -->
</div>
</div>
<!-- Bootstrap JS Bundle with Popper -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.bundle.min.js"
integrity="sha384-MrcW6ZMFYlzcLA8Nl+NtUVF0sA7MsXsP1UyJoMp4YLEuNSfAP+JcXn/tWtIaxVXM"
crossorigin="anonymous"></script>
</body>
<div></div>
</html>

View File

@ -1,8 +1,17 @@
{% extends "base.html" %}
{% block content %}
{% if logged_in %}
<div class="container py-5 h-100">
<br>
<div class="container-fluid">
<div class="row align-items-stretch">
<div id="menu" class="col-md-2 bg-light">
<!-- internal menu -->
<h2>Menu</h2>
<ul>
<li>Web links</li>
<li><a href="/useradmin">User Administration</a></li>
</ul>
</div>
<div class="col-8">
<p>This will be the private information area for the extended Jean-Marie family.</p>
<div>
<h2>Web links</h2>
@ -21,6 +30,12 @@
<li><a href="https://www.geni.com">Geni</a></li>
</ul>
</div>
</div>
<div id="events" class="col-2 bg-light">
<!-- events -->
<h2>Events</h2>
</div>
</div>
</div>
{% else %}
<!-- Carousel -->
@ -35,21 +50,21 @@
<div class="carousel-inner">
<div class="carousel-item active">
<img src="assets/images/slide-01.jpg" alt="View from the dock" class="d-block w-50">
<img src="assets/images/slide-01.jpg" alt="View from the dock" class="d-block w-100">
<div class="carousel-caption">
<h3>Jean-Marie Family</h3>
<p>View from the dock</p>
</div>
</div>
<div class="carousel-item">
<img src="assets/images/slide-02.jpg" alt="Sunset at 3 sisters" class="d-block w-50">
<img src="assets/images/slide-02.jpg" alt="Sunset at 3 sisters" class="d-block w-100">
<div class="carousel-caption">
<h3>Jean-Marie Family</h3>
<p>Sunset over the 3 sisters</p>
</div>
</div>
<div class="carousel-item">
<img src="assets/images/slide-03.jpg" alt="Purple sunset" class="d-block w-50">
<img src="assets/images/slide-03.jpg" alt="Purple sunset" class="d-block w-100">
<div class="carousel-caption">
<h3>Jean-Marie Family</h3>
<p>Purple sunset</p>
@ -66,5 +81,5 @@
<span class="carousel-control-next-icon"></span>
</button>
</div>
{% endif %}
{% endblock content %}
{% endif %}
{% endblock content %}

View File

@ -2,8 +2,8 @@
{% block title %}User Profile{% endblock %}
{% block content %}
<h1>User Profile</h1>
Full name: {{ name }}<br/>
Given name: {{ given_name }}<br/>
Family name: {{ family_name }}<br/>
Your email address: {{ email }}<br
Full name: {{ user.name }}<br/>
Given name: {{ user.given_name }}<br/>
Family name: {{ user.family_name }}<br/>
Your email address: {{ user.email }}<br
{% endblock %}

View File

@ -2,12 +2,22 @@
{% block title %}User Profile{% endblock %}
{% block content %}
<h1>Users</h1>
<table>
<table class="table table-striped">
<thead>
<tr>
<th scope="col">ID</th>
<th scope="col">Name</th>
<th scope="col">email</th>
</tr>
</thead>
<tbody>
{% for user in users %}
<tr>
<td>{{ user.id }}</td>
<td><a href="/users/{{ user.id }}">{{ user.id }}</a></td>
<td>{{ user.name }}</td>
<td>{{ user.email }}</td>
</tr>
{% endfor %}
</tbody>
</table>
{% endblock %}