initial profile page work
This commit is contained in:
parent
779e4aa2ea
commit
2dbc6ad68a
|
@ -6,7 +6,9 @@ CREATE TABLE users (
|
|||
host TEXT,
|
||||
display_name TEXT,
|
||||
email TEXT NOT NULL,
|
||||
password_hash TEXT NOT NULL
|
||||
password_hash TEXT NOT NULL,
|
||||
avatar_uri TEXT,
|
||||
bio TEXT,
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX u_username_host ON users (username, host);
|
||||
|
|
|
@ -17,17 +17,10 @@ impl Users {
|
|||
|
||||
pub async fn create_user(&self, u: User) -> Result<User, db::DBError> {
|
||||
let row = self.0.lock().await.query_one(
|
||||
"insert into users (id, username, host, display_name, password_hash, email) values ($1, $2, $3, $4, $5, $6) returning id",
|
||||
"insert into users (id, username, host, display_name, password_hash, email) values ($1, $2, $3, $4, $5, $6) returning (id, username, host, display_name, password_hash, email, avatar_uri, bio)",
|
||||
&[&sec::new_id(), &u.username, &u.host, &u.display_name, &u.password_hash, &u.email],
|
||||
).await?;
|
||||
Ok(User {
|
||||
id: row.get("id"),
|
||||
username: u.username,
|
||||
host: u.host,
|
||||
display_name: u.display_name,
|
||||
password_hash: u.password_hash,
|
||||
email: u.email,
|
||||
})
|
||||
Ok(User::from(&row))
|
||||
}
|
||||
|
||||
pub async fn user(&self, by: UserSelect) -> Result<User, db::DBError> {
|
||||
|
@ -52,7 +45,7 @@ impl Users {
|
|||
.await
|
||||
.query(
|
||||
format!(
|
||||
"select id, username, host, display_name, password_hash, email from users where {}",
|
||||
"select id, username, host, display_name, password_hash, email, avatar_uri, bio from users where {}",
|
||||
where_clause
|
||||
)
|
||||
.as_str(),
|
||||
|
@ -75,6 +68,8 @@ pub struct User {
|
|||
pub display_name: Option<String>,
|
||||
pub password_hash: String,
|
||||
pub email: String,
|
||||
pub avatar_uri: Option<String>,
|
||||
pub bio: Option<String>,
|
||||
}
|
||||
|
||||
impl From<&Row> for User {
|
||||
|
@ -86,6 +81,8 @@ impl From<&Row> for User {
|
|||
display_name: row.get("display_name"),
|
||||
password_hash: row.get("password_hash"),
|
||||
email: row.get("email"),
|
||||
avatar_uri: row.get("avatar_uri"),
|
||||
bio: row.get("bio"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,20 +1,16 @@
|
|||
use std::{collections::HashMap, str::FromStr};
|
||||
use std;
|
||||
|
||||
use axum::{
|
||||
body::Full,
|
||||
extract::Path,
|
||||
handler::Handler,
|
||||
http::{header, HeaderValue, StatusCode},
|
||||
response::{self, Html, IntoResponse, Response},
|
||||
routing, Extension, Form, Json, Router,
|
||||
response::{self, IntoResponse, Response},
|
||||
routing, Extension, Form, Router,
|
||||
};
|
||||
use mime_guess::mime;
|
||||
use tower_cookies::{Cookie, Cookies};
|
||||
|
||||
use crate::svc::{
|
||||
auth::{AuthError, Claims},
|
||||
profiles,
|
||||
};
|
||||
use crate::svc::auth::{AuthError, Claims};
|
||||
|
||||
use super::{
|
||||
servek::{Server, ServerError},
|
||||
|
@ -45,15 +41,26 @@ impl Server {
|
|||
.fallback(routing::get(Self::handler_404))
|
||||
}
|
||||
|
||||
fn from_cookie(&self, cookie: Option<Cookie>) -> Result<WithNav<Option<Claims>>, ServerError> {
|
||||
if let Some(cookie) = cookie {
|
||||
let claims = self.auth.get_claims(cookie.value().to_owned())?;
|
||||
fn from_cookies(&self, cookies: Cookies) -> Result<WithNav<Option<Claims>>, ServerError> {
|
||||
const logged_out: Result<WithNav<Option<Claims>>, ServerError> = Ok(WithNav {
|
||||
obj: None,
|
||||
nav_type: NavType::LoggedOut,
|
||||
});
|
||||
if let Some(cookie) = cookies.get(AUTH_COOKIE_NAME) {
|
||||
let claims = match self.auth.get_claims(cookie.value().to_owned()) {
|
||||
Ok(claims) => claims,
|
||||
Err(e) => {
|
||||
if e.clone().expired() {
|
||||
cookies.remove(Cookie::new(AUTH_COOKIE_NAME, ""));
|
||||
return logged_out;
|
||||
} else {
|
||||
return Err(e.into());
|
||||
}
|
||||
}
|
||||
};
|
||||
Ok(WithNav::new(Some(claims.clone()), claims.into()))
|
||||
} else {
|
||||
Ok(WithNav {
|
||||
nav_type: NavType::LoggedOut,
|
||||
obj: None,
|
||||
})
|
||||
logged_out
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -61,7 +68,7 @@ impl Server {
|
|||
Extension(srv): Extension<Server>,
|
||||
cookies: Cookies,
|
||||
) -> Result<impl IntoResponse, ServerError> {
|
||||
let user = srv.from_cookie(cookies.get(AUTH_COOKIE_NAME))?;
|
||||
let user = srv.from_cookies(cookies)?;
|
||||
Ok((
|
||||
StatusCode::OK,
|
||||
response::Html(srv.hb.render("index", &user)?),
|
||||
|
@ -89,10 +96,7 @@ impl Server {
|
|||
) -> Result<impl IntoResponse, ServerError> {
|
||||
Ok((
|
||||
StatusCode::OK,
|
||||
response::Html(
|
||||
srv.hb
|
||||
.render("404", &srv.from_cookie(cookies.get(AUTH_COOKIE_NAME))?)?,
|
||||
),
|
||||
response::Html(srv.hb.render("404", &srv.from_cookies(cookies)?)?),
|
||||
))
|
||||
}
|
||||
|
||||
|
@ -149,7 +153,21 @@ impl Server {
|
|||
)?,
|
||||
));
|
||||
}
|
||||
let token = srv.auth.login(login.username, login.password).await?;
|
||||
let token = match srv.auth.login(login.username, login.password).await {
|
||||
Ok(token) => token,
|
||||
Err(e) => match e {
|
||||
AuthError::InvalidCredentials => {
|
||||
return Ok((
|
||||
StatusCode::UNAUTHORIZED,
|
||||
srv.login_page_with(
|
||||
"error-partial".to_owned(),
|
||||
"invalid credentials".to_owned(),
|
||||
)?,
|
||||
))
|
||||
}
|
||||
e => return Err(e.into()),
|
||||
},
|
||||
};
|
||||
|
||||
if cookies.get(AUTH_COOKIE_NAME).is_some() {
|
||||
cookies.remove(Cookie::new(AUTH_COOKIE_NAME, ""));
|
||||
|
@ -178,7 +196,7 @@ impl Server {
|
|||
"profile",
|
||||
&WithNav::new(
|
||||
srv.profiler.profile(username).await?,
|
||||
srv.from_cookie(cookies.get(AUTH_COOKIE_NAME))?.nav_type,
|
||||
srv.from_cookies(cookies)?.nav_type,
|
||||
),
|
||||
)?),
|
||||
))
|
||||
|
|
|
@ -105,6 +105,7 @@ impl From<AuthError> for ServerError {
|
|||
ServerError::BadRequest("invalid credentials".to_owned())
|
||||
}
|
||||
AuthError::ServerError(err) => ServerError::Internal(err),
|
||||
AuthError::Expired => ServerError::BadRequest("expired token".to_owned()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -95,12 +95,19 @@ impl From<users::User> for Claims {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum AuthError {
|
||||
InvalidCredentials,
|
||||
Expired,
|
||||
ServerError(String),
|
||||
}
|
||||
|
||||
impl AuthError {
|
||||
pub fn expired(self) -> bool {
|
||||
self == Self::Expired
|
||||
}
|
||||
}
|
||||
|
||||
impl From<DBError> for AuthError {
|
||||
fn from(_: DBError) -> Self {
|
||||
Self::InvalidCredentials
|
||||
|
@ -109,6 +116,9 @@ impl From<DBError> for AuthError {
|
|||
|
||||
impl From<jsonwebtoken::errors::Error> for AuthError {
|
||||
fn from(e: jsonwebtoken::errors::Error) -> Self {
|
||||
Self::ServerError(e.to_string())
|
||||
match e.kind() {
|
||||
jsonwebtoken::errors::ErrorKind::ExpiredSignature => Self::Expired,
|
||||
kind => Self::ServerError(e.to_string()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -42,6 +42,8 @@ impl Profiler {
|
|||
display_name: None,
|
||||
password_hash: sec::hash(password),
|
||||
email,
|
||||
avatar_uri: None,
|
||||
bio: None,
|
||||
})
|
||||
.await?;
|
||||
|
||||
|
@ -55,15 +57,26 @@ pub struct User {
|
|||
pub username: String,
|
||||
pub display_name: Option<String>,
|
||||
pub host: Option<String>,
|
||||
pub avatar_uri: Option<String>,
|
||||
pub bio: Option<String>,
|
||||
}
|
||||
|
||||
impl From<users::User> for User {
|
||||
fn from(u: users::User) -> Self {
|
||||
Self {
|
||||
id: u.id,
|
||||
display_name: u.display_name.or(Some(format!(
|
||||
"@{}{}",
|
||||
u.username,
|
||||
u.host
|
||||
.clone()
|
||||
.map(|h| format!("@{}", h))
|
||||
.unwrap_or(String::new()),
|
||||
))),
|
||||
username: u.username,
|
||||
display_name: u.display_name,
|
||||
host: u.host,
|
||||
avatar_uri: u.avatar_uri,
|
||||
bio: u.bio,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,7 +3,6 @@ body {
|
|||
color: rebeccapurple;
|
||||
}
|
||||
|
||||
p,
|
||||
h1,
|
||||
h2,
|
||||
label {
|
||||
|
@ -62,11 +61,58 @@ a {
|
|||
color: rebeccapurple;
|
||||
}
|
||||
|
||||
.main {
|
||||
width: 85vw;
|
||||
border: 5px solid rebeccapurple;
|
||||
height: 85vh;
|
||||
padding: 10px;
|
||||
margin: 0 auto;
|
||||
display: flex;
|
||||
background-color: rgba(13, 6, 19, 0.4);
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.avatar {
|
||||
width: 128px;
|
||||
height: 128px;
|
||||
border: 3px solid rebeccapurple;
|
||||
}
|
||||
|
||||
.name {
|
||||
font-size: 110%;
|
||||
text-align: center;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.profile {
|
||||
/* border: 3px solid rebeccapurple; */
|
||||
padding: 5px;
|
||||
display: flex;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.profile-header {
|
||||
text-align: left;
|
||||
width: 128px;
|
||||
}
|
||||
|
||||
.profile-bio {
|
||||
border: 3px solid rebeccapurple;
|
||||
margin: auto;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
nav {
|
||||
/* background-color: #333; */
|
||||
margin: 0;
|
||||
overflow: hidden;
|
||||
border: 5px solid rgba(102, 51, 153, 0.5);
|
||||
width: 85vw;
|
||||
padding-left: 10px;
|
||||
padding-right: 10px;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
margin-bottom: 10px;
|
||||
background-color: rgba(13, 6, 19, 0.4);
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
nav ul {
|
||||
|
|
|
@ -2,14 +2,24 @@
|
|||
<html>
|
||||
|
||||
<head>
|
||||
<title>@{{username}}</title>
|
||||
<title>@{{obj.username}}</title>
|
||||
<link rel="stylesheet" href="/static/style/main.css">
|
||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
{{> (lookup this "nav_type") }}
|
||||
<h1>hi {{obj.username}}, your id is {{obj.id}}</h1>
|
||||
<div class="main">
|
||||
<div class="profile">
|
||||
<div class="profile-header">
|
||||
<img class="avatar" src="{{obj.avatar_uri}}" alt="{{obj.username}}'s avatar" />
|
||||
<p class="name">{{obj.display_name}}</p>
|
||||
</div>
|
||||
<div class="profile-bio">
|
||||
<p>{{obj.bio}}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
|
|
Loading…
Reference in New Issue