Fix loading of profiles from user administration screen
This commit is contained in:
parent
8905d05b01
commit
4f152df006
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
use std::path::Path;
|
||||
|
||||
use super::{AppError, UserData};
|
||||
use axum::{
|
||||
body::Body,
|
||||
|
|
@ -43,9 +45,13 @@ 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)
|
||||
.fetch_one(&db_pool)
|
||||
.await?;
|
||||
let row = sqlx::query_as!(
|
||||
UserData,
|
||||
"SELECT * FROM users WHERE id = ?",
|
||||
id
|
||||
)
|
||||
.fetch_one(&db_pool)
|
||||
.await?;
|
||||
|
||||
request.extensions_mut().insert(Some(UserData {
|
||||
id: row.id,
|
||||
|
|
@ -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;
|
||||
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,
|
||||
_ => 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())
|
||||
.bind(request.extensions().get::<Option<UserData>>().unwrap().as_ref().unwrap().email.as_str())
|
||||
.fetch_one(&app_state)
|
||||
.await,
|
||||
.await
|
||||
}
|
||||
};
|
||||
|
||||
// user is logged in
|
||||
|
|
|
|||
|
|
@ -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)]
|
||||
|
|
|
|||
|
|
@ -10,50 +10,50 @@
|
|||
<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">
|
||||
<!-- Links -->
|
||||
<ul class="navbar-nav">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/">Home</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/about">About</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/contactus">Contact Us</a>
|
||||
</li>
|
||||
{% if logged_in %}
|
||||
<li class="nav-item"><a class="nav-link" href="/logout">Logout</a></li>
|
||||
<li class="nav-item"><a class="nav-link" href="/profile">{{ name }}</a></li>
|
||||
{% else %}
|
||||
<li class="nav-item"><a class="nav-link" href="/login">Login</a></li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
<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">
|
||||
<a class="nav-link" href="/">Home</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/about">About</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/contactus">Contact Us</a>
|
||||
</li>
|
||||
{% if logged_in %}
|
||||
<li class="nav-item"><a class="nav-link" href="/logout">Logout</a></li>
|
||||
<li class="nav-item"><a class="nav-link" href="/profile">{{ name }}</a></li>
|
||||
{% else %}
|
||||
<li class="nav-item"><a class="nav-link" href="/login">Login</a></li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
</nav>
|
||||
|
||||
{% block content %}{% endblock content %}
|
||||
|
||||
<div class="container marketing">
|
||||
<!-- FOOTER -->
|
||||
<footer>
|
||||
<p class="pull-right"><a href="#">Back to top</a></p>
|
||||
<p>© 2024 Jean-Marie family</p>
|
||||
</footer>
|
||||
</div><!-- /.container -->
|
||||
<div class="row flex-grow-1">
|
||||
{% block content %}{% endblock content %}
|
||||
</div>
|
||||
<div class="row fixed-bottom sticky-bottom">
|
||||
<div class="container-fluid text-center bg-light">
|
||||
<!-- FOOTER -->
|
||||
<footer>
|
||||
<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>
|
||||
|
|
@ -1,70 +1,85 @@
|
|||
{% extends "base.html" %}
|
||||
{% block content %}
|
||||
{% if logged_in %}
|
||||
<div class="container py-5 h-100">
|
||||
<br>
|
||||
<p>This will be the private information area for the extended Jean-Marie family.</p>
|
||||
<div>
|
||||
<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>
|
||||
<h3>TLC Creations</h3>
|
||||
<ul>
|
||||
<li><a href="https://www.tlccreations.ca">TLC Creations</a></li>
|
||||
<li><a href="https://www.tlccreations.ca">TLC Creations</a></li>
|
||||
</ul>
|
||||
<h3>Fonts</h3>
|
||||
<ul>
|
||||
<li><a href="https://fonts.google.com">Google fonts</a></li>
|
||||
<li><a href="https://www.fontspace.com">Font Space</a></li>
|
||||
<li><a href="https://fonts.google.com">Google fonts</a></li>
|
||||
<li><a href="https://www.fontspace.com">Font Space</a></li>
|
||||
</ul>
|
||||
<h3>Family tree</h3>
|
||||
<ul>
|
||||
<li><a href="https://www.ancestry.com">Ancestry</a></li>
|
||||
<li><a href="https://www.geni.com">Geni</a></li>
|
||||
<li><a href="https://www.ancestry.com">Ancestry</a></li>
|
||||
<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 -->
|
||||
<div id="demo" class="carousel slide" data-bs-ride="carousel">
|
||||
|
||||
<!-- Indicators/dots -->
|
||||
<div class="carousel-indicators">
|
||||
<button type="button" data-bs-target="#demo" data-bs-slide-to="0" class="active"></button>
|
||||
<button type="button" data-bs-target="#demo" data-bs-slide-to="1"></button>
|
||||
<button type="button" data-bs-target="#demo" data-bs-slide-to="2"></button>
|
||||
<!-- Indicators/dots -->
|
||||
<div class="carousel-indicators">
|
||||
<button type="button" data-bs-target="#demo" data-bs-slide-to="0" class="active"></button>
|
||||
<button type="button" data-bs-target="#demo" data-bs-slide-to="1"></button>
|
||||
<button type="button" data-bs-target="#demo" data-bs-slide-to="2"></button>
|
||||
</div>
|
||||
|
||||
<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-100">
|
||||
<div class="carousel-caption">
|
||||
<h3>Jean-Marie Family</h3>
|
||||
<p>View from the dock</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<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">
|
||||
<div class="carousel-caption">
|
||||
<h3>Jean-Marie Family</h3>
|
||||
<p>View from the dock</p>
|
||||
</div>
|
||||
<div class="carousel-item">
|
||||
<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 class="carousel-item">
|
||||
<img src="assets/images/slide-02.jpg" alt="Sunset at 3 sisters" class="d-block w-50">
|
||||
<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">
|
||||
<div class="carousel-caption">
|
||||
<h3>Jean-Marie Family</h3>
|
||||
<p>Purple sunset</p>
|
||||
</div>
|
||||
</div>
|
||||
<!-- The slideshow/carousel -->
|
||||
|
||||
|
||||
<!-- Left and right controls/icons -->
|
||||
<button class="carousel-control-prev" type="button" data-bs-target="#demo" data-bs-slide="prev">
|
||||
<span class="carousel-control-prev-icon"></span>
|
||||
</button>
|
||||
<button class="carousel-control-next" type="button" data-bs-target="#demo" data-bs-slide="next">
|
||||
<span class="carousel-control-next-icon"></span>
|
||||
</button>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endblock content %}
|
||||
<div class="carousel-item">
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
<!-- The slideshow/carousel -->
|
||||
|
||||
|
||||
<!-- Left and right controls/icons -->
|
||||
<button class="carousel-control-prev" type="button" data-bs-target="#demo" data-bs-slide="prev">
|
||||
<span class="carousel-control-prev-icon"></span>
|
||||
</button>
|
||||
<button class="carousel-control-next" type="button" data-bs-target="#demo" data-bs-slide="next">
|
||||
<span class="carousel-control-next-icon"></span>
|
||||
</button>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endblock content %}
|
||||
|
|
@ -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 %}
|
||||
|
|
|
|||
|
|
@ -2,12 +2,22 @@
|
|||
{% block title %}User Profile{% endblock %}
|
||||
{% block content %}
|
||||
<h1>Users</h1>
|
||||
<table>
|
||||
{% for user in users %}
|
||||
<tr>
|
||||
<td>{{ user.id }}</td>
|
||||
<td>{{ user.email }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
<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><a href="/users/{{ user.id }}">{{ user.id }}</a></td>
|
||||
<td>{{ user.name }}</td>
|
||||
<td>{{ user.email }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% endblock %}
|
||||
Loading…
Reference in New Issue